/* 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 #include #include #include #include #include #include #include #include #include #include #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); }