319 lines
6.8 KiB
C
319 lines
6.8 KiB
C
|
/* 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);
|
||
|
}
|
||
|
|