David van Moolenbroek 10b1b4ee12 devman: allow multiple event read calls up to EOF
Read calls may be repeated by VFS if the user destination memory is
not mapped in.  Devman currently assumes that all reads are
successful, and uses this to track whether EOF has been reached for
a particular event, discarding it as soon as this happens.  Upon
repetition, this may result in lost events for devmand.

With this patch, devman discards events only once devmand reads the
EOF marker, which itself can never generate a user page fault.  The
result is that read calls for data can be repeated safely, without
the risk of losing events in the process.

Change-Id: I9dfdf7f8c8992a20a10302d79c3506e61f8564b0
2014-10-28 14:43:33 +00:00

521 lines
13 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;
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);
*len = buf_get(ptr);
/* read all (EOF)? */
if (ev != NULL && *len == 0) {
TAILQ_REMOVE(&n->event_queue, ev, events);
free(ev);
}
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;
}