minix3/servers/devman/device.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;
}