sos-code-article10/sos/chardev.c

464 lines
13 KiB
C

/* 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 <sos/assert.h>
#include <sos/types.h>
#include <sos/fs.h>
#include <sos/list.h>
#include <sos/kmalloc.h>
#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
};