/* Copyright (C) 2005 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 "chardev.h" struct sos_chardev_class { sos_ui32_t device_class; struct sos_chardev_ops *ops; /** This corresponds to the chardev_class_custom_data field passed to open/read/etc. and to sos_chardev_register_class() */ void *custom_data; sos_count_t ref_cnt; /**< increased each time a FS node is opened */ struct sos_chardev_class *next, *prev; }; struct sos_chardev_opened_file { /** "normal" VFS opened file structure is available in this structure */ struct sos_fs_opened_file super; /** Additional information for this opened file: the device's class structure */ struct sos_chardev_class *class; }; /** The list of registered classes, ie the dictionary major number -> device class description */ static struct sos_chardev_class *registered_chardev_classes; /* Forward declarations */ static struct sos_fs_ops_opened_file chardev_ops_opened_file; static struct sos_fs_ops_opened_chardev chardev_ops_opened_chardev; static sos_ret_t chardev_helper_new_opened_file(struct sos_fs_node * this, const struct sos_process * owner, sos_ui32_t open_flags, struct sos_fs_opened_file ** result_of); static sos_ret_t chardev_helper_close_opened_file(struct sos_fs_node * this, struct sos_fs_opened_file * of); static sos_ret_t duplicate_opened_chardev(struct sos_fs_opened_file *this, const struct sos_process * for_owner, struct sos_fs_opened_file **result); /** * Return the device descriptionn structure for the corresponding * device class, or NULL when none found. */ static struct sos_chardev_class * lookup_chardev_class(sos_ui32_t device_class) { struct sos_chardev_class *chardev; int nb; list_foreach (registered_chardev_classes, chardev, nb) { if (chardev->device_class == device_class) return chardev; } return NULL; } sos_ret_t sos_chardev_register_class (sos_ui32_t device_class, struct sos_chardev_ops *ops, void * chardev_class_custom_data) { struct sos_chardev_class *chardev; /* Make sure this device class is not already registered */ chardev = lookup_chardev_class(device_class); if (NULL != chardev) return -SOS_EBUSY; /* Allocate and initialize a new device description */ chardev = (struct sos_chardev_class *) sos_kmalloc(sizeof(struct sos_chardev_class), 0); if (chardev == NULL) return -SOS_ENOMEM; chardev->device_class = device_class; chardev->custom_data = chardev_class_custom_data; chardev->ops = ops; chardev->ref_cnt = 1; /* insert it into the list */ list_add_tail (registered_chardev_classes, chardev); return SOS_OK; } sos_ret_t sos_chardev_unregister_class (sos_ui32_t device_class) { struct sos_chardev_class *chardev; /* Make sure this device class is already registered */ chardev = lookup_chardev_class(device_class); if (NULL == chardev) return -SOS_ENODEV; /* Make sure no files are already opened for it */ if (chardev->ref_cnt != 1) return -SOS_EBUSY; /* remove it from the list */ list_delete (registered_chardev_classes, chardev); return sos_kfree((sos_vaddr_t)chardev); } sos_ret_t sos_chardev_helper_ref_new_fsnode(struct sos_fs_node * this) { this->new_opened_file = chardev_helper_new_opened_file; this->close_opened_file = chardev_helper_close_opened_file; return SOS_OK; } sos_ret_t sos_chardev_helper_release_fsnode(struct sos_fs_node * this) { return SOS_OK; } /** Callback called each time an FS-node is opened by a user process: create a new sos_chardev_opened_file object associated to the correct major number, and call the device driver's open method */ static sos_ret_t chardev_helper_new_opened_file(struct sos_fs_node * this, const struct sos_process * owner, sos_ui32_t open_flags, struct sos_fs_opened_file ** result_of) { sos_ret_t retval; struct sos_chardev_opened_file *chardev_of; /* Lookup the character device description structure */ struct sos_chardev_class * chardev = lookup_chardev_class(this->dev_id.device_class); if (NULL == chardev) return -SOS_ENODEV; /* Alloocate the new "open file" description structure */ chardev_of = (struct sos_chardev_opened_file*) sos_kmalloc(sizeof(struct sos_chardev_opened_file), 0); if (NULL == chardev_of) return -SOS_ENOMEM; memset(chardev_of, 0x0, sizeof(struct sos_chardev_opened_file)); chardev_of->class = chardev; *result_of = & chardev_of->super; /* Increase the reference coount for that node */ SOS_ASSERT_FATAL(chardev->ref_cnt >= 1); chardev->ref_cnt ++; /* Initialize the read/write/seek callbacks */ (*result_of)->owner = owner; (*result_of)->open_flags = open_flags; (*result_of)->ops_file = & chardev_ops_opened_file; (*result_of)->ops_chardev = & chardev_ops_opened_chardev; /* Call the open callback */ retval = chardev->ops->open(this, & chardev_of->super, chardev->custom_data); if (SOS_OK != retval) { sos_kfree((sos_vaddr_t) chardev_of); chardev->ref_cnt --; *result_of = NULL; return retval; } /* Specify the duplicate method */ (*result_of)->duplicate = duplicate_opened_chardev; return retval; } /** Callback called each time an opened file is closed. Un-allocate the associated sos_chardev_opened_file object */ static sos_ret_t chardev_helper_close_opened_file(struct sos_fs_node * this, struct sos_fs_opened_file * of) { sos_ret_t retval; struct sos_chardev_opened_file *chardev_of = ((struct sos_chardev_opened_file*)of); struct sos_chardev_class * chardev = chardev_of->class; SOS_ASSERT_FATAL(NULL != chardev); /* Free the new "open file" description structure */ if (NULL != chardev->ops->close) retval = chardev->ops->close(& chardev_of->super, chardev->custom_data); else retval = SOS_OK; if (SOS_OK != retval) return retval; /* Decrease the reference coount for that node */ SOS_ASSERT_FATAL(chardev->ref_cnt > 1); chardev->ref_cnt --; sos_kfree((sos_vaddr_t) chardev_of); return retval; } /** * Callback called each time a process is "forked": create a new * sos_chardev_opened_file for the new process. * * @note Almost identical to the open callback. */ static sos_ret_t duplicate_opened_chardev(struct sos_fs_opened_file *this, const struct sos_process * for_owner, struct sos_fs_opened_file **result) { sos_ret_t retval; struct sos_chardev_opened_file *chardev_of = ((struct sos_chardev_opened_file*)this); struct sos_chardev_opened_file *new_chardev_of; struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); *result = NULL; /* Lookup the character device description structure */ struct sos_chardev_class * chardev = chardev_of->class; SOS_ASSERT_FATAL(NULL != chardev); /* Allocate a new duplicate copy of the original opened file */ new_chardev_of = (struct sos_chardev_opened_file*) sos_kmalloc(sizeof(struct sos_chardev_opened_file), 0); if (NULL == new_chardev_of) return -SOS_ENOMEM; memcpy(new_chardev_of, chardev_of, sizeof(*new_chardev_of)); new_chardev_of->super.owner = for_owner; new_chardev_of->super.direntry = NULL; /* Reset the direntry for the new opened file */ SOS_ASSERT_FATAL(chardev->ref_cnt > 1); chardev->ref_cnt ++; retval = chardev->ops->open(fsnode, & new_chardev_of->super, chardev->custom_data); if (SOS_OK != retval) { sos_kfree((sos_vaddr_t) new_chardev_of); chardev->ref_cnt --; return retval; } /* Make sure the required methods are overloaded */ SOS_ASSERT_FATAL(NULL != new_chardev_of->super.ops_file); SOS_ASSERT_FATAL(NULL != new_chardev_of->super.ops_file->seek); SOS_ASSERT_FATAL(NULL != new_chardev_of->super.ops_file->read); *result = & new_chardev_of->super; return retval; } /* * FS generic character device wrapper functions */ /** * Callback called to change the position in the opened file: call the * seek method of the device driver. */ static sos_ret_t chardev_wrap_seek(struct sos_fs_opened_file *this, sos_lsoffset_t offset, sos_seek_whence_t whence, /* out */ sos_lsoffset_t * result_position) { sos_ret_t retval = -SOS_ENOSYS; struct sos_chardev_opened_file *chardev_of = ((struct sos_chardev_opened_file*)this); struct sos_chardev_class * chardev = chardev_of->class; SOS_ASSERT_FATAL(NULL != chardev); SOS_ASSERT_FATAL(NULL != chardev->ops); if (NULL != chardev->ops->seek) retval = chardev->ops->seek(this, offset, whence, result_position); return retval; } /** * Callback called to read the contents of the opened file: call the * read method of the device driver. */ static sos_ret_t chardev_wrap_read(struct sos_fs_opened_file *this, sos_uaddr_t dest_buf, sos_size_t * /* in/out */len) { sos_ret_t retval = -SOS_ENOSYS; struct sos_chardev_opened_file *chardev_of = ((struct sos_chardev_opened_file*)this); struct sos_chardev_class * chardev = chardev_of->class; SOS_ASSERT_FATAL(NULL != chardev); SOS_ASSERT_FATAL(NULL != chardev->ops); if (NULL != chardev->ops->read) retval = chardev->ops->read(this, dest_buf, len); return retval; } /** * Callback called to write bytes to the opened file: call the write * method of the device driver. */ static sos_ret_t chardev_wrap_write(struct sos_fs_opened_file *this, sos_uaddr_t src_buf, sos_size_t * /* in/out */len) { sos_ret_t retval = -SOS_ENOSYS; struct sos_chardev_opened_file *chardev_of = ((struct sos_chardev_opened_file*)this); struct sos_chardev_class * chardev = chardev_of->class; SOS_ASSERT_FATAL(NULL != chardev); SOS_ASSERT_FATAL(NULL != chardev->ops); if (NULL != chardev->ops->write) retval = chardev->ops->write(this, src_buf, len); return retval; } /** * Callback called to map the contents of the opened file: call the * map method of the device driver. */ static sos_ret_t chardev_wrap_mmap(struct sos_fs_opened_file *this, sos_uaddr_t *uaddr, sos_size_t size, sos_ui32_t access_rights, sos_ui32_t flags, sos_luoffset_t offset) { sos_ret_t retval = -SOS_ENOSYS; struct sos_chardev_opened_file *chardev_of = ((struct sos_chardev_opened_file*)this); struct sos_chardev_class * chardev = chardev_of->class; SOS_ASSERT_FATAL(NULL != chardev); SOS_ASSERT_FATAL(NULL != chardev->ops); if (NULL != chardev->ops->mmap) retval = chardev->ops->mmap(this, uaddr, size, access_rights, flags, offset); return retval; } /** * Callback called to change the state of the opened file: call the * fcntl method of the device driver. */ static sos_ret_t chardev_wrap_fcntl(struct sos_fs_opened_file *this, int req_id, sos_ui32_t req_arg) { sos_ret_t retval = -SOS_ENOSYS; struct sos_chardev_opened_file *chardev_of = ((struct sos_chardev_opened_file*)this); struct sos_chardev_class * chardev = chardev_of->class; SOS_ASSERT_FATAL(NULL != chardev); SOS_ASSERT_FATAL(NULL != chardev->ops); if (NULL != chardev->ops->fcntl) retval = chardev->ops->fcntl(this, req_id, req_arg); return retval; } /** * Callback called to control the underlying device: call the ioctl * method of the device driver. */ static sos_ret_t chardev_wrap_ioctl(struct sos_fs_opened_file *this, int req_id, sos_ui32_t req_arg) { sos_ret_t retval = -SOS_ENOSYS; struct sos_chardev_opened_file *chardev_of = ((struct sos_chardev_opened_file*)this); struct sos_chardev_class * chardev = chardev_of->class; SOS_ASSERT_FATAL(NULL != chardev); SOS_ASSERT_FATAL(NULL != chardev->ops); if (NULL != chardev->ops->ioctl) retval = chardev->ops->ioctl(this, req_id, req_arg); return retval; } /** * Gather the callbacks for a "character device" opened file */ static struct sos_fs_ops_opened_file chardev_ops_opened_file = (struct sos_fs_ops_opened_file) { .seek = chardev_wrap_seek, .read = chardev_wrap_read, .write = chardev_wrap_write, .mmap = chardev_wrap_mmap, .fcntl = chardev_wrap_fcntl }; static struct sos_fs_ops_opened_chardev chardev_ops_opened_chardev = (struct sos_fs_ops_opened_chardev) { .ioctl = chardev_wrap_ioctl };