#include #include #include #include #include #include #include #include struct ft_buffer* alloc_ft_buffer(unsigned int count, size_t size) { struct ft_buffer* buf; size_t total = (size + 1) * count; char* mem; int order = 0, pages = 1; buf = kmalloc(sizeof(*buf), GFP_KERNEL); if (!buf) return NULL; total = (total / PAGE_SIZE) + (total % PAGE_SIZE != 0); while (pages < total) { order++; pages *= 2; } mem = (char*) __get_free_pages(GFP_KERNEL, order); if (!mem) { kfree(buf); return NULL; } if (!init_ft_buffer(buf, count, size, mem + (count * size), /* markers at the end */ mem)) { /* buffer objects */ free_pages((unsigned long) mem, order); kfree(buf); return NULL; } return buf; } void free_ft_buffer(struct ft_buffer* buf) { int order = 0, pages = 1; size_t total; if (buf) { total = (buf->slot_size + 1) * buf->slot_count; total = (total / PAGE_SIZE) + (total % PAGE_SIZE != 0); while (pages < total) { order++; pages *= 2; } free_pages((unsigned long) buf->buffer_mem, order); kfree(buf); } } struct ftdev_event { int id; struct ftdev_event* next; }; static int activate(struct ftdev_event** chain, int id) { struct ftdev_event* ev = kmalloc(sizeof(*ev), GFP_KERNEL); if (ev) { printk(KERN_INFO "Enabling feather-trace event %d.\n", (int) id); ft_enable_event(id); ev->id = id; ev->next = *chain; *chain = ev; } return ev ? 0 : -ENOMEM; } static void deactivate(struct ftdev_event** chain, int id) { struct ftdev_event **cur = chain; struct ftdev_event *nxt; while (*cur) { if ((*cur)->id == id) { nxt = (*cur)->next; kfree(*cur); *cur = nxt; printk(KERN_INFO "Disabling feather-trace event %d.\n", (int) id); ft_disable_event(id); break; } cur = &(*cur)->next; } } static int ftdev_open(struct inode *in, struct file *filp) { struct ftdev* ftdev; struct ftdev_minor* ftdm; unsigned int buf_idx = iminor(in); int err = 0; ftdev = container_of(in->i_cdev, struct ftdev, cdev); if (buf_idx >= ftdev->minor_cnt) { err = -ENODEV; goto out; } if (ftdev->can_open && (err = ftdev->can_open(ftdev, buf_idx))) goto out; ftdm = ftdev->minor + buf_idx; filp->private_data = ftdm; if (mutex_lock_interruptible(&ftdm->lock)) { err = -ERESTARTSYS; goto out; } if (!ftdm->readers && ftdev->alloc) err = ftdev->alloc(ftdev, buf_idx); if (0 == err) ftdm->readers++; mutex_unlock(&ftdm->lock); out: return err; } static int ftdev_release(struct inode *in, struct file *filp) { struct ftdev* ftdev; struct ftdev_minor* ftdm; unsigned int buf_idx = iminor(in); int err = 0; ftdev = container_of(in->i_cdev, struct ftdev, cdev); if (buf_idx >= ftdev->minor_cnt) { err = -ENODEV; goto out; } ftdm = ftdev->minor + buf_idx; if (mutex_lock_interruptible(&ftdm->lock)) { err = -ERESTARTSYS; goto out; } if (ftdm->readers == 1) { while (ftdm->events) deactivate(&ftdm->events, ftdm->events->id); /* wait for any pending events to complete */ set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(HZ); printk(KERN_ALERT "Failed trace writes: %u\n", ftdm->buf->failed_writes); if (ftdev->free) ftdev->free(ftdev, buf_idx); } ftdm->readers--; mutex_unlock(&ftdm->lock); out: return err; } /* based on ft_buffer_read * @returns < 0 : page fault * = 0 : no data available * = 1 : one slot copied */ static int ft_buffer_copy_to_user(struct ft_buffer* buf, char __user *dest) { unsigned int idx; int err = 0; if (buf->free_count != buf->slot_count) { /* data available */ idx = buf->read_idx % buf->slot_count; if (buf->slots[idx] == SLOT_READY) { err = copy_to_user(dest, ((char*) buf->buffer_mem) + idx * buf->slot_size, buf->slot_size); if (err == 0) { /* copy ok */ buf->slots[idx] = SLOT_FREE; buf->read_idx++; fetch_and_inc(&buf->free_count); err = 1; } } } return err; } static ssize_t ftdev_read(struct file *filp, char __user *to, size_t len, loff_t *f_pos) { /* we ignore f_pos, this is strictly sequential */ ssize_t err = 0; size_t chunk; int copied; struct ftdev_minor* ftdm = filp->private_data; if (mutex_lock_interruptible(&ftdm->lock)) { err = -ERESTARTSYS; goto out; } chunk = ftdm->buf->slot_size; while (len >= chunk) { copied = ft_buffer_copy_to_user(ftdm->buf, to); if (copied == 1) { len -= chunk; to += chunk; err += chunk; } else if (err == 0 && copied == 0 && ftdm->events) { /* Only wait if there are any events enabled and only * if we haven't copied some data yet. We cannot wait * here with copied data because that data would get * lost if the task is interrupted (e.g., killed). */ set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(50); if (signal_pending(current)) { if (err == 0) /* nothing read yet, signal problem */ err = -ERESTARTSYS; break; } } else if (copied < 0) { /* page fault */ err = copied; break; } else /* nothing left to get, return to user space */ break; } mutex_unlock(&ftdm->lock); out: return err; } typedef uint32_t cmd_t; static ssize_t ftdev_write(struct file *filp, const char __user *from, size_t len, loff_t *f_pos) { struct ftdev_minor* ftdm = filp->private_data; ssize_t err = -EINVAL; cmd_t cmd; cmd_t id; if (len % sizeof(cmd) || len < 2 * sizeof(cmd)) goto out; if (copy_from_user(&cmd, from, sizeof(cmd))) { err = -EFAULT; goto out; } len -= sizeof(cmd); from += sizeof(cmd); if (cmd != FTDEV_ENABLE_CMD && cmd != FTDEV_DISABLE_CMD) goto out; if (mutex_lock_interruptible(&ftdm->lock)) { err = -ERESTARTSYS; goto out; } err = sizeof(cmd); while (len) { if (copy_from_user(&id, from, sizeof(cmd))) { err = -EFAULT; goto out_unlock; } /* FIXME: check id against list of acceptable events */ len -= sizeof(cmd); from += sizeof(cmd); if (cmd == FTDEV_DISABLE_CMD) deactivate(&ftdm->events, id); else if (activate(&ftdm->events, id) != 0) { err = -ENOMEM; goto out_unlock; } err += sizeof(cmd); } out_unlock: mutex_unlock(&ftdm->lock); out: return err; } struct file_operations ftdev_fops = { .owner = THIS_MODULE, .open = ftdev_open, .release = ftdev_release, .write = ftdev_write, .read = ftdev_read, }; void ftdev_init(struct ftdev* ftdev, struct module* owner) { int i; cdev_init(&ftdev->cdev, &ftdev_fops); ftdev->cdev.owner = owner; ftdev->cdev.ops = &ftdev_fops; ftdev->minor_cnt = 0; for (i = 0; i < MAX_FTDEV_MINORS; i++) { mutex_init(&ftdev->minor[i].lock); ftdev->minor[i].readers = 0; ftdev->minor[i].buf = NULL; ftdev->minor[i].events = NULL; } ftdev->alloc = NULL; ftdev->free = NULL; ftdev->can_open = NULL; } int register_ftdev(struct ftdev* ftdev, const char* name, int major) { dev_t trace_dev; int error = 0; if(major) { trace_dev = MKDEV(major, 0); error = register_chrdev_region(trace_dev, ftdev->minor_cnt, name); } else { error = alloc_chrdev_region(&trace_dev, 0, ftdev->minor_cnt, name); major = MAJOR(trace_dev); } if (error) { printk(KERN_WARNING "ftdev(%s): " "Could not register major/minor number %d/%u\n", name, major, ftdev->minor_cnt); return error; } error = cdev_add(&ftdev->cdev, trace_dev, ftdev->minor_cnt); if (error) { printk(KERN_WARNING "ftdev(%s): " "Could not add cdev for major/minor = %d/%u.\n", name, major, ftdev->minor_cnt); return error; } return error; }