464 lines
13 KiB
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
|
|
};
|