sos-code-article10/drivers/tty.c

319 lines
6.8 KiB
C
Raw Normal View History

2018-07-13 17:13:10 +02:00
/* Copyright (C) 2005 Thomas Petazzoni, David Decotigny
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
*/
#include <sos/types.h>
#include <sos/assert.h>
#include <sos/errno.h>
#include <sos/fs.h>
#include <sos/ksynch.h>
#include <sos/kwaitq.h>
#include <sos/uaccess.h>
#include <sos/kmalloc.h>
#include <sos/list.h>
#include <sos/chardev.h>
#include <drivers/devices.h>
#include "tty.h"
#define TTY_BUFFER_LEN 64
#define TTY_NAME_LEN 16
struct tty_device {
sos_ui32_t instance;
unsigned int open_count;
char buffer[TTY_BUFFER_LEN];
unsigned int buffer_read;
unsigned int buffer_write;
struct sos_ksema sem;
struct sos_kwaitq wq;
sos_ret_t (*write)(char c);
unsigned int param;
struct tty_device *next, *prev;
};
static struct tty_device *tty_device_list;
static struct tty_device *
tty_device_find (sos_ui32_t instance)
{
struct tty_device *t;
int n;
list_foreach (tty_device_list, t, n)
{
if (t->instance == instance)
return t;
}
return NULL;
}
static sos_ret_t tty_read(struct sos_fs_opened_file *this,
sos_uaddr_t dest_buf, sos_size_t *len)
{
sos_size_t count = 0;
struct tty_device *t;
sos_ret_t ret;
t = (struct tty_device *) this->custom_data;
if (*len == 0)
return SOS_OK;
while (1)
{
char c;
/* Take the semaphore */
sos_ksema_down (& t->sem, NULL);
/* No keys available in the ring buffer, wait until the
add_chars callback wakes us up */
if (t->buffer_read == t->buffer_write)
{
sos_ksema_up (& t->sem);
sos_kwaitq_wait (& t->wq, NULL);
/* Go back to begining of loop: maybe someone else stole the
semaphore */
continue;
}
c = t->buffer[t->buffer_read];
/* Copy the received character from the ring buffer to the
destination buffer */
ret = sos_memcpy_to_user (dest_buf,
(sos_vaddr_t) & t->buffer[t->buffer_read],
sizeof (char));
if (sizeof(char) != ret)
{
*len = count;
sos_ksema_up (& t->sem);
return ret;
}
dest_buf++;
/* Update the ring buffer read pointer */
t->buffer_read++;
if (t->buffer_read == TTY_BUFFER_LEN)
t->buffer_read = 0;
count++;
if (t->param & SOS_IOCTLPARAM_TTY_ECHO)
t->write (c);
sos_ksema_up (& t->sem);
/* Did we read enough bytes ? */
if (count == *len
|| (c == '\n' && t->param & SOS_IOCTLPARAM_TTY_CANON))
break;
}
*len = count;
return SOS_OK;
}
static sos_ret_t tty_write(struct sos_fs_opened_file *this,
sos_uaddr_t src_buf, sos_size_t *len)
{
struct tty_device *t;
char c;
unsigned int i;
sos_ret_t ret;
t = (struct tty_device *) this->custom_data;
for (i = 0; i < *len; i++)
{
ret = sos_memcpy_from_user ((sos_vaddr_t) & c, src_buf, sizeof(char));
if (sizeof(char) != ret)
{
*len = i;
return ret;
}
sos_ksema_down (& t->sem, NULL);
t->write (c);
sos_ksema_up (& t->sem);
src_buf++;
}
return SOS_OK;
}
static sos_ret_t tty_ioctl(struct sos_fs_opened_file *this, int req_id,
sos_ui32_t req_arg)
{
struct tty_device *t;
if (req_arg != SOS_IOCTLPARAM_TTY_ECHO
&& req_arg != SOS_IOCTLPARAM_TTY_CANON)
return -SOS_EINVAL;
t = (struct tty_device *) this->custom_data;
sos_ksema_down (& t->sem, NULL);
switch (req_id)
{
case SOS_IOCTL_TTY_SETPARAM:
t->param |= req_arg;
break;
case SOS_IOCTL_TTY_RESETPARAM:
t->param &= ~req_arg;
break;
default:
sos_ksema_up (& t->sem);
return -SOS_EINVAL;
}
sos_ksema_up (& t->sem);
return SOS_OK;
}
static sos_ret_t tty_open(struct sos_fs_node * fsnode,
struct sos_fs_opened_file * of,
void * chardev_class_custom_data)
{
struct tty_device *t;
t = tty_device_find (fsnode->dev_id.device_instance);
if (t == NULL)
return -SOS_ENOENT;
sos_ksema_down (& t->sem, NULL);
of->custom_data = t;
t->open_count ++;
sos_ksema_up (& t->sem);
return SOS_OK;
}
static sos_ret_t tty_close(struct sos_fs_opened_file *of,
void *custom_data)
{
struct tty_device *t;
t = (struct tty_device *) of->custom_data;
sos_ksema_down (& t->sem, NULL);
t->open_count --;
sos_ksema_up (& t->sem);
return SOS_OK;
}
void tty_add_chars (struct tty_device *t, const char *s)
{
sos_ksema_down (& t->sem, NULL);
while (*s)
{
/* Add all characters to the ring buffer */
t->buffer[t->buffer_write] = *s;
t->buffer_write++;
if (t->buffer_write == TTY_BUFFER_LEN)
t->buffer_write = 0;
s++;
}
sos_ksema_up (& t->sem);
/* Wake up a possibly waiting thread */
sos_kwaitq_wakeup (& t->wq, SOS_KWQ_WAKEUP_ALL, SOS_OK);
}
struct sos_chardev_ops tty_ops = {
.read = tty_read,
.write = tty_write,
.open = tty_open,
.close = tty_close,
.ioctl = tty_ioctl
};
sos_ret_t tty_create (sos_ui32_t device_instance,
sos_ret_t (*write_func) (char c),
struct tty_device **tty_out)
{
struct tty_device *tty;
if (tty_device_find (device_instance) != NULL)
return -SOS_EBUSY;
tty = (struct tty_device *) sos_kmalloc (sizeof(struct tty_device), 0);
if (tty == NULL)
return -SOS_ENOMEM;
memset (tty->buffer, 0, sizeof(tty->buffer));
tty->open_count = 0;
tty->instance = device_instance;
tty->write = write_func;
tty->buffer_read = 0;
tty->buffer_write = 0;
tty->param = SOS_IOCTLPARAM_TTY_CANON;
sos_kwaitq_init(& tty->wq, "tty", SOS_KWQ_ORDER_FIFO);
sos_ksema_init(& tty->sem, "tty", 1, SOS_KWQ_ORDER_FIFO);
list_add_tail (tty_device_list, tty);
*tty_out = tty;
return SOS_OK;
}
sos_ret_t tty_remove (struct tty_device *tty)
{
if (tty == NULL)
return -SOS_EINVAL;
if (SOS_OK != sos_ksema_trydown (& tty->sem))
return -SOS_EBUSY;
if (tty->open_count != 0)
return -SOS_EBUSY;
sos_kwaitq_dispose (& tty->wq);
list_delete (tty_device_list, tty);
sos_kfree ((sos_vaddr_t) tty);
return SOS_OK;
}
sos_ret_t tty_subsystem_setup (void)
{
list_init (tty_device_list);
return sos_chardev_register_class (SOS_CHARDEV_TTY_MAJOR,
& tty_ops, NULL);
}
sos_ret_t tty_subsystem_cleanup (void)
{
return sos_chardev_unregister_class (SOS_CHARDEV_TTY_MAJOR);
}