528 lines
14 KiB
C
528 lines
14 KiB
C
#include "devman.h"
|
|
#include "proto.h"
|
|
|
|
|
|
static struct devman_device*devman_dev_add_child(struct devman_device
|
|
*parent, struct devman_device_info *devinf);
|
|
static struct devman_device *_find_dev(struct devman_device *dev, int
|
|
dev_id);
|
|
static int devman_dev_add_info(struct devman_device *dev, struct
|
|
devman_device_info_entry *entry, char *buf);
|
|
static int devman_event_read(char **ptr, size_t *len,off_t offset, void
|
|
*data);
|
|
|
|
static int devman_del_device(struct devman_device *dev);
|
|
|
|
static int next_device_id = 1;
|
|
|
|
static struct inode_stat default_dir_stat = {
|
|
/* .mode = */ S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH,
|
|
/* .uid = */ 0,
|
|
/* .gid = */ 0,
|
|
/* .size = */ 0,
|
|
/* .dev = */ NO_DEV,
|
|
};
|
|
|
|
static struct inode_stat default_file_stat = {
|
|
/* .mode = */ S_IFREG | S_IRUSR | S_IRGRP | S_IROTH,
|
|
/* .uid = */ 0,
|
|
/* .gid = */ 0,
|
|
/* .size = */ 0x1000,
|
|
/* .dev = */ NO_DEV,
|
|
};
|
|
|
|
|
|
static struct devman_device root_dev;
|
|
static struct devman_event_inode event_inode_data = {
|
|
TAILQ_HEAD_INITIALIZER(event_inode_data.event_queue),
|
|
};
|
|
static struct devman_inode event_inode;
|
|
|
|
/*===========================================================================*
|
|
* devman_generate_path *
|
|
*===========================================================================*/
|
|
static int
|
|
devman_generate_path(char* buf, int len, struct devman_device *dev)
|
|
{
|
|
int res =0;
|
|
const char * name = ".";
|
|
const char * sep = "/";
|
|
|
|
if (dev != NULL) {
|
|
res = devman_generate_path(buf, len, dev->parent);
|
|
if (res != 0) {
|
|
return res;
|
|
}
|
|
name = get_inode_name(dev->inode.inode);
|
|
} else {
|
|
}
|
|
|
|
/* does it fit? */
|
|
if (strlen(buf) + strlen(name) + strlen(sep) + 1 > len) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
strcat(buf, name);
|
|
strcat(buf, sep);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* devman_device_add_event *
|
|
*===========================================================================*/
|
|
static void
|
|
devman_device_add_event(struct devman_device* dev)
|
|
{
|
|
struct devman_event * event;
|
|
char buf[12]; /* this fits the device ID " 0xXXXXXXXX" */
|
|
int res;
|
|
|
|
event = malloc(sizeof(struct devman_event));
|
|
|
|
if (event == NULL) {
|
|
panic("devman_device_remove_event: out of memory\n");
|
|
}
|
|
|
|
memset(event, 0, sizeof(*event));
|
|
|
|
strncpy(event->data, ADD_STRING, DEVMAN_STRING_LEN - 1);
|
|
|
|
res = devman_generate_path(event->data, DEVMAN_STRING_LEN - 11 , dev);
|
|
|
|
if (res) {
|
|
panic("devman_device_add_event: "
|
|
"devman_generate_path failed: (%d)\n", res);
|
|
}
|
|
|
|
snprintf(buf, 12, " 0x%08x", dev->dev_id);
|
|
strcat(event->data,buf);
|
|
|
|
TAILQ_INSERT_HEAD(&event_inode_data.event_queue, event, events);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* devman_device_remove_event *
|
|
*===========================================================================*/
|
|
static void
|
|
devman_device_remove_event(struct devman_device* dev)
|
|
{
|
|
struct devman_event * event;
|
|
char buf[12]; /* this fits the device ID " 0xXXXXXXXX" */
|
|
int res;
|
|
|
|
event = malloc(sizeof(struct devman_event));
|
|
|
|
if (event == NULL) {
|
|
panic("devman_device_remove_event: out of memory\n");
|
|
}
|
|
|
|
memset(event, 0, sizeof(*event));
|
|
|
|
strncpy(event->data, REMOVE_STRING, DEVMAN_STRING_LEN - 1);
|
|
|
|
res = devman_generate_path(event->data, DEVMAN_STRING_LEN-11, dev);
|
|
|
|
if (res) {
|
|
panic("devman_device_remove_event: "
|
|
"devman_generate_path failed: (%d)\n", res);
|
|
}
|
|
|
|
snprintf(buf, 12, " 0x%08x", dev->dev_id);
|
|
strcat(event->data,buf);
|
|
|
|
|
|
TAILQ_INSERT_HEAD(&event_inode_data.event_queue, event, events);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* devman_event_read *
|
|
*===========================================================================*/
|
|
static int
|
|
devman_event_read(char **ptr, size_t *len,off_t offset, void *data)
|
|
{
|
|
struct devman_event *ev = NULL;
|
|
struct devman_event_inode *n;
|
|
static int eof = 0;
|
|
|
|
if (eof) {
|
|
*len=0;
|
|
eof = 0;
|
|
return 0;
|
|
}
|
|
n = (struct devman_event_inode *) data;
|
|
|
|
if (!TAILQ_EMPTY(&n->event_queue)) {
|
|
ev = TAILQ_LAST(&n->event_queue, event_head);
|
|
}
|
|
|
|
buf_init(offset, *len);
|
|
if (ev != NULL) {
|
|
buf_printf("%s", ev->data);
|
|
/* read all? */
|
|
if (*len + offset >= strlen(ev->data)) {
|
|
TAILQ_REMOVE(&n->event_queue, ev, events);
|
|
free(ev);
|
|
eof = 1;
|
|
}
|
|
}
|
|
|
|
*len = buf_get(ptr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* devman_static_info_read *
|
|
*===========================================================================*/
|
|
static int
|
|
devman_static_info_read(char **ptr, size_t *len, off_t offset, void *data)
|
|
{
|
|
struct devman_static_info_inode *n;
|
|
|
|
n = (struct devman_static_info_inode *) data;
|
|
|
|
buf_init(offset, *len);
|
|
buf_printf("%s\n", n->data);
|
|
*len = buf_get(ptr);
|
|
return 0;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* devman_init_devices *
|
|
*===========================================================================*/
|
|
void devman_init_devices()
|
|
{
|
|
event_inode.data = &event_inode_data;
|
|
event_inode.read_fn = devman_event_read;
|
|
|
|
root_dev.dev_id = 0;
|
|
root_dev.major = -1;
|
|
root_dev.owner = 0;
|
|
root_dev.parent = NULL;
|
|
|
|
root_dev.inode.inode=
|
|
add_inode(get_root_inode(), "devices",
|
|
NO_INDEX, &default_dir_stat, 0, &root_dev.inode);
|
|
|
|
event_inode.inode=
|
|
add_inode(get_root_inode(), "events",
|
|
NO_INDEX, &default_file_stat, 0, &event_inode);
|
|
|
|
TAILQ_INIT(&root_dev.children);
|
|
TAILQ_INIT(&root_dev.infos);
|
|
}
|
|
|
|
|
|
/*===========================================================================*
|
|
* do_reply *
|
|
*===========================================================================*/
|
|
static void do_reply(message *msg, int res)
|
|
{
|
|
msg->m_type = DEVMAN_REPLY;
|
|
msg->DEVMAN_RESULT = res;
|
|
ipc_send(msg->m_source, msg);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* do_add_device *
|
|
*===========================================================================*/
|
|
int do_add_device(message *msg)
|
|
{
|
|
endpoint_t ep = msg->m_source;
|
|
int res;
|
|
struct devman_device *dev;
|
|
struct devman_device *parent;
|
|
struct devman_device_info *devinf = NULL;
|
|
|
|
devinf = malloc(msg->DEVMAN_GRANT_SIZE);
|
|
|
|
if (devinf == NULL) {
|
|
res = ENOMEM;
|
|
do_reply(msg, res);
|
|
return 0;
|
|
}
|
|
|
|
res = sys_safecopyfrom(ep, msg->DEVMAN_GRANT_ID,
|
|
0, (vir_bytes) devinf, msg->DEVMAN_GRANT_SIZE);
|
|
|
|
if (res != OK) {
|
|
res = EINVAL;
|
|
free(devinf);
|
|
do_reply(msg, res);
|
|
return 0;
|
|
}
|
|
|
|
if ((parent = _find_dev(&root_dev, devinf->parent_dev_id))
|
|
== NULL) {
|
|
res = ENODEV;
|
|
free(devinf);
|
|
do_reply(msg, res);
|
|
return 0;
|
|
}
|
|
|
|
dev = devman_dev_add_child(parent, devinf);
|
|
|
|
if (dev == NULL) {
|
|
res = ENODEV;
|
|
free(devinf);
|
|
do_reply(msg, res);
|
|
return 0;
|
|
}
|
|
|
|
dev->state = DEVMAN_DEVICE_UNBOUND;
|
|
|
|
dev->owner = msg->m_source;
|
|
|
|
msg->DEVMAN_DEVICE_ID = dev->dev_id;
|
|
|
|
devman_device_add_event(dev);
|
|
|
|
do_reply(msg, res);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*===========================================================================*
|
|
* _find_dev *
|
|
*===========================================================================*/
|
|
static struct devman_device *
|
|
_find_dev(struct devman_device *dev, int dev_id)
|
|
{
|
|
struct devman_device *_dev;
|
|
|
|
if(dev->dev_id == dev_id)
|
|
return dev;
|
|
|
|
TAILQ_FOREACH(_dev, &dev->children, siblings) {
|
|
|
|
struct devman_device *t = _find_dev(_dev, dev_id);
|
|
|
|
if (t !=NULL) {
|
|
return t;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* devman_find_dev *
|
|
*===========================================================================*/
|
|
struct devman_device *devman_find_device(int dev_id)
|
|
{
|
|
return _find_dev(&root_dev, dev_id);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* devman_dev_add_static_info *
|
|
*===========================================================================*/
|
|
static int
|
|
devman_dev_add_static_info
|
|
(struct devman_device *dev, char * name, char *data)
|
|
{
|
|
struct devman_inode *inode;
|
|
struct devman_static_info_inode *st_inode;
|
|
|
|
|
|
st_inode = malloc(sizeof(struct devman_static_info_inode));
|
|
st_inode->dev = dev;
|
|
|
|
strncpy(st_inode->data, data, DEVMAN_STRING_LEN);
|
|
/* if string is longer it's truncated */
|
|
st_inode->data[DEVMAN_STRING_LEN-1] = 0;
|
|
|
|
inode = malloc (sizeof(struct devman_inode));
|
|
inode->data = st_inode;
|
|
inode->read_fn = devman_static_info_read;
|
|
|
|
inode->inode = add_inode(dev->inode.inode, name,
|
|
NO_INDEX, &default_file_stat, 0, inode);
|
|
|
|
/* add info to info_list */
|
|
TAILQ_INSERT_HEAD(&dev->infos, inode, inode_list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* devman_dev_add_child *
|
|
*===========================================================================*/
|
|
static struct devman_device*
|
|
devman_dev_add_child
|
|
(struct devman_device *parent, struct devman_device_info *devinf)
|
|
{
|
|
int i;
|
|
char * buffer = (char *) (devinf);
|
|
char tmp_buf[128];
|
|
struct devman_device_info_entry *entries;
|
|
|
|
/* create device */
|
|
struct devman_device * dev = malloc(sizeof(struct devman_device));
|
|
if (dev == NULL) {
|
|
panic("devman_dev_add_child: out of memory\n");
|
|
}
|
|
|
|
|
|
if (parent == NULL) {
|
|
free(dev);
|
|
return NULL;
|
|
}
|
|
|
|
dev->ref_count = 1;
|
|
|
|
/* set dev_info */
|
|
dev->parent = parent;
|
|
dev->info = devinf;
|
|
|
|
dev->dev_id = next_device_id++;
|
|
|
|
dev->inode.inode =
|
|
add_inode(parent->inode.inode, buffer + devinf->name_offset,
|
|
NO_INDEX, &default_dir_stat, 0, &dev->inode);
|
|
|
|
TAILQ_INIT(&dev->children);
|
|
TAILQ_INIT(&dev->infos);
|
|
|
|
/* create information inodes */
|
|
entries = (struct devman_device_info_entry *)
|
|
(buffer + sizeof(struct devman_device_info));
|
|
|
|
for (i = 0; i < devinf->count ; i++) {
|
|
devman_dev_add_info(dev, &entries[i], buffer);
|
|
}
|
|
|
|
/* make device ID accessible to user land */
|
|
snprintf(tmp_buf, DEVMAN_STRING_LEN, "%d",dev->dev_id);
|
|
devman_dev_add_static_info(dev, "devman_id", tmp_buf);
|
|
|
|
TAILQ_INSERT_HEAD(&parent->children, dev, siblings);
|
|
|
|
devman_get_device(parent);
|
|
|
|
/* FUTURE TODO: create links(BUS, etc) */
|
|
return dev;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* devman_dev_add_info *
|
|
*===========================================================================*/
|
|
static int
|
|
devman_dev_add_info
|
|
(struct devman_device *dev, struct devman_device_info_entry *entry, char *buf)
|
|
{
|
|
switch(entry->type) {
|
|
|
|
case DEVMAN_DEVINFO_STATIC:
|
|
return devman_dev_add_static_info(dev,
|
|
buf + entry->name_offset, buf + entry->data_offset);
|
|
|
|
case DEVMAN_DEVINFO_DYNAMIC:
|
|
/* TODO */
|
|
/* fall through */
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* do_del_device *
|
|
*===========================================================================*/
|
|
int do_del_device(message *msg)
|
|
{
|
|
int dev_id = msg->DEVMAN_DEVICE_ID;
|
|
|
|
int res=0;
|
|
|
|
/* only parrent is allowed to add devices */
|
|
struct devman_device *dev = _find_dev(&root_dev, dev_id);
|
|
|
|
if (dev == NULL ) {
|
|
printf("devman: no dev with id %d\n",dev_id);
|
|
res = ENODEV;
|
|
}
|
|
|
|
#if 0
|
|
if (dev->parent->owner != ep) {
|
|
res = EPERM;
|
|
}
|
|
#endif
|
|
|
|
if (!res) {
|
|
devman_device_remove_event(dev);
|
|
if (dev->state == DEVMAN_DEVICE_BOUND) {
|
|
dev->state = DEVMAN_DEVICE_ZOMBIE;
|
|
}
|
|
devman_put_device(dev);
|
|
}
|
|
|
|
do_reply(msg, res);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* devman_get_device *
|
|
*===========================================================================*/
|
|
void devman_get_device(struct devman_device *dev)
|
|
{
|
|
if (dev == NULL || dev == &root_dev) {
|
|
return;
|
|
}
|
|
dev->ref_count++;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* devman_put_device *
|
|
*===========================================================================*/
|
|
void devman_put_device(struct devman_device *dev)
|
|
{
|
|
if (dev == NULL || dev == &root_dev ) {
|
|
return;
|
|
}
|
|
dev->ref_count--;
|
|
if (dev->ref_count == 0) {
|
|
devman_del_device(dev);
|
|
}
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* devman_del_device *
|
|
*===========================================================================*/
|
|
static int devman_del_device(struct devman_device *dev)
|
|
{
|
|
/* does device have children -> error */
|
|
/* evtl. remove links */
|
|
|
|
/* free devinfo inodes */
|
|
struct devman_inode *inode, *_inode;
|
|
|
|
TAILQ_FOREACH_SAFE(inode, &dev->infos, inode_list, _inode) {
|
|
|
|
delete_inode(inode->inode);
|
|
|
|
TAILQ_REMOVE(&dev->infos, inode, inode_list);
|
|
|
|
if (inode->data) {
|
|
free(inode->data);
|
|
}
|
|
|
|
free(inode);
|
|
}
|
|
|
|
/* free device inode */
|
|
delete_inode(dev->inode.inode);
|
|
|
|
/* remove from parent */
|
|
TAILQ_REMOVE(&dev->parent->children, dev, siblings);
|
|
|
|
devman_put_device(dev->parent);
|
|
|
|
/* free devinfo */
|
|
free(dev->info);
|
|
|
|
/* free device */
|
|
free(dev);
|
|
return 0;
|
|
}
|