diff options
author | Javier Martinez Canillas <martinez.javier@gmail.com> | 2010-11-27 07:49:17 +0100 |
---|---|---|
committer | Javier Martinez Canillas <martinez.javier@gmail.com> | 2010-11-27 07:49:17 +0100 |
commit | ab121f379a3cff458c90e6f480ba4bb68c8733dd (patch) | |
tree | a9851af109ee83646d108bc247d03b131461b764 | |
download | ldd3-ab121f379a3cff458c90e6f480ba4bb68c8733dd.tar.gz |
Linux Device Drivers 3 examples
99 files changed, 13768 insertions, 0 deletions
@@ -0,0 +1,27 @@ + + +Unless otherwise stated, the source code distributed with this book +can be redistributed in source or binary form so long as an +acknowledgment appears in derived source files. The citation should +list that the code comes from BOOK by AUTHOR, published by O'Reilly & +Associates. This code is under copyright and cannot be included in +any other book, publication, or educational product without permission +from O'Reilly & Associates. No warranty is attached; we cannot take +responsibility for errors or fitness for use. + + +There are a few exception to this licence, however: a few sources +herein are distributed according to the GNU General Public License, +version 2. You'll find a copy of the license in +/usr/src/linux/COPYING, and in other places in your filesystem. The +affected source files are: + + pci/pci_skel.c + tty/tiny_serial.c + tty/tiny_tty.c + usb/usb-skeleton.c + +The files in ./pci ./tty and ./usb inherit the GPL from the kernel +sources, as most of their code comes straight from the kernel +(usb-skeleton.c being part of the kernel source tree directly.) + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e3e7fc2 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ + +SUBDIRS = misc-progs misc-modules \ + skull scull scullc sculld scullp scullv sbull snull\ + short shortprint pci simple usb tty lddbus + +all: subdirs + +subdirs: + for n in $(SUBDIRS); do $(MAKE) -C $$n || exit 1; done + +clean: + for n in $(SUBDIRS); do $(MAKE) -C $$n clean; done diff --git a/include/lddbus.h b/include/lddbus.h new file mode 100644 index 0000000..a6b349e --- /dev/null +++ b/include/lddbus.h @@ -0,0 +1,39 @@ +/* + * Definitions for the virtual LDD bus. + * + * $Id: lddbus.h,v 1.4 2004/08/20 18:49:44 corbet Exp $ + */ + +//extern struct device ldd_bus; +extern struct bus_type ldd_bus_type; + + +/* + * The LDD driver type. + */ + +struct ldd_driver { + char *version; + struct module *module; + struct device_driver driver; + struct driver_attribute version_attr; +}; + +#define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver); + +/* + * A device type for things "plugged" into the LDD bus. + */ + +struct ldd_device { + char *name; + struct ldd_driver *driver; + struct device dev; +}; + +#define to_ldd_device(dev) container_of(dev, struct ldd_device, dev); + +extern int register_ldd_device(struct ldd_device *); +extern void unregister_ldd_device(struct ldd_device *); +extern int register_ldd_driver(struct ldd_driver *); +extern void unregister_ldd_driver(struct ldd_driver *); diff --git a/lddbus/Makefile b/lddbus/Makefile new file mode 100644 index 0000000..1a3ab78 --- /dev/null +++ b/lddbus/Makefile @@ -0,0 +1,39 @@ +# Comment/uncomment the following line to disable/enable debugging +#DEBUG = y + +# Add your debugging flag (or not) to CFLAGS +ifeq ($(DEBUG),y) + DEBFLAGS = -O -g # "-O" is needed to expand inlines +else + DEBFLAGS = -O2 +endif +CFLAGS += $(DEBFLAGS) -I$(LDDINCDIR) + + +ifneq ($(KERNELRELEASE),) +# call from kernel build system + +obj-m := lddbus.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +default: + $(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) LDDINCDIR=$(PWD)/../include modules + +endif + + + +clean: + rm -rf *.o *.ko *~ core .depend *.mod.c .*.cmd .tmp_versions .*.o.d + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/lddbus/lddbus.c b/lddbus/lddbus.c new file mode 100644 index 0000000..be37c6c --- /dev/null +++ b/lddbus/lddbus.c @@ -0,0 +1,177 @@ +/* + * A virtual bus for LDD sample code devices to plug into. This + * code is heavily borrowed from drivers/base/sys.c + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + */ +/* $Id: lddbus.c,v 1.9 2004/09/26 08:12:27 gregkh Exp $ */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/string.h> +#include "lddbus.h" + +MODULE_AUTHOR("Jonathan Corbet"); +MODULE_LICENSE("Dual BSD/GPL"); +static char *Version = "$Revision: 1.9 $"; + +/* + * Respond to hotplug events. + */ +static int ldd_hotplug(struct device *dev, char **envp, int num_envp, + char *buffer, int buffer_size) +{ + envp[0] = buffer; + if (snprintf(buffer, buffer_size, "LDDBUS_VERSION=%s", + Version) >= buffer_size) + return -ENOMEM; + envp[1] = NULL; + return 0; +} + +/* + * Match LDD devices to drivers. Just do a simple name test. + */ +static int ldd_match(struct device *dev, struct device_driver *driver) +{ + return !strncmp(dev->bus_id, driver->name, strlen(driver->name)); +} + + +/* + * The LDD bus device. + */ +static void ldd_bus_release(struct device *dev) +{ + printk(KERN_DEBUG "lddbus release\n"); +} + +struct device ldd_bus = { + .bus_id = "ldd0", + .release = ldd_bus_release +}; + + +/* + * And the bus type. + */ +struct bus_type ldd_bus_type = { + .name = "ldd", + .match = ldd_match, + .hotplug = ldd_hotplug, +}; + +/* + * Export a simple attribute. + */ +static ssize_t show_bus_version(struct bus_type *bus, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", Version); +} + +static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL); + + + +/* + * LDD devices. + */ + +/* + * For now, no references to LDDbus devices go out which are not + * tracked via the module reference count, so we use a no-op + * release function. + */ +static void ldd_dev_release(struct device *dev) +{ } + +int register_ldd_device(struct ldd_device *ldddev) +{ + ldddev->dev.bus = &ldd_bus_type; + ldddev->dev.parent = &ldd_bus; + ldddev->dev.release = ldd_dev_release; + strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE); + return device_register(&ldddev->dev); +} +EXPORT_SYMBOL(register_ldd_device); + +void unregister_ldd_device(struct ldd_device *ldddev) +{ + device_unregister(&ldddev->dev); +} +EXPORT_SYMBOL(unregister_ldd_device); + +/* + * Crude driver interface. + */ + + +static ssize_t show_version(struct device_driver *driver, char *buf) +{ + struct ldd_driver *ldriver = to_ldd_driver(driver); + + sprintf(buf, "%s\n", ldriver->version); + return strlen(buf); +} + + +int register_ldd_driver(struct ldd_driver *driver) +{ + int ret; + + driver->driver.bus = &ldd_bus_type; + ret = driver_register(&driver->driver); + if (ret) + return ret; + driver->version_attr.attr.name = "version"; + driver->version_attr.attr.owner = driver->module; + driver->version_attr.attr.mode = S_IRUGO; + driver->version_attr.show = show_version; + driver->version_attr.store = NULL; + return driver_create_file(&driver->driver, &driver->version_attr); +} + +void unregister_ldd_driver(struct ldd_driver *driver) +{ + driver_unregister(&driver->driver); +} +EXPORT_SYMBOL(register_ldd_driver); +EXPORT_SYMBOL(unregister_ldd_driver); + + + +static int __init ldd_bus_init(void) +{ + int ret; + + ret = bus_register(&ldd_bus_type); + if (ret) + return ret; + if (bus_create_file(&ldd_bus_type, &bus_attr_version)) + printk(KERN_NOTICE "Unable to create version attribute\n"); + ret = device_register(&ldd_bus); + if (ret) + printk(KERN_NOTICE "Unable to register ldd0\n"); + return ret; +} + +static void ldd_bus_exit(void) +{ + device_unregister(&ldd_bus); + bus_unregister(&ldd_bus_type); +} + +module_init(ldd_bus_init); +module_exit(ldd_bus_exit); diff --git a/misc-modules/Makefile b/misc-modules/Makefile new file mode 100644 index 0000000..d04d2d6 --- /dev/null +++ b/misc-modules/Makefile @@ -0,0 +1,32 @@ + +# To build modules outside of the kernel tree, we run "make" +# in the kernel source tree; the Makefile these then includes this +# Makefile once again. +# This conditional selects whether we are being included from the +# kernel Makefile or not. +ifeq ($(KERNELRELEASE),) + + # Assume the source tree is where the running kernel was built + # You should set KERNELDIR in the environment if it's elsewhere + KERNELDIR ?= /lib/modules/$(shell uname -r)/build + # The current directory is passed to sub-makes as argument + PWD := $(shell pwd) + +modules: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +modules_install: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + +.PHONY: modules modules_install clean + +else + # called from kernel build system: just declare what our modules are + obj-m := hello.o hellop.o seq.o jit.o jiq.o sleepy.o complete.o \ + silly.o faulty.o kdatasize.o kdataalign.o +endif + + diff --git a/misc-modules/complete.c b/misc-modules/complete.c new file mode 100644 index 0000000..700923f --- /dev/null +++ b/misc-modules/complete.c @@ -0,0 +1,81 @@ +/* + * complete.c -- the writers awake the readers + * + * Copyright (C) 2003 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2003 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: complete.c,v 1.2 2004/09/26 07:02:43 gregkh Exp $ + */ + +#include <linux/module.h> +#include <linux/init.h> + +#include <linux/sched.h> /* current and everything */ +#include <linux/kernel.h> /* printk() */ +#include <linux/fs.h> /* everything... */ +#include <linux/types.h> /* size_t */ +#include <linux/completion.h> + +MODULE_LICENSE("Dual BSD/GPL"); + +static int complete_major = 0; + +DECLARE_COMPLETION(comp); + +ssize_t complete_read (struct file *filp, char __user *buf, size_t count, loff_t *pos) +{ + printk(KERN_DEBUG "process %i (%s) going to sleep\n", + current->pid, current->comm); + wait_for_completion(&comp); + printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm); + return 0; /* EOF */ +} + +ssize_t complete_write (struct file *filp, const char __user *buf, size_t count, + loff_t *pos) +{ + printk(KERN_DEBUG "process %i (%s) awakening the readers...\n", + current->pid, current->comm); + complete(&comp); + return count; /* succeed, to avoid retrial */ +} + + +struct file_operations complete_fops = { + .owner = THIS_MODULE, + .read = complete_read, + .write = complete_write, +}; + + +int complete_init(void) +{ + int result; + + /* + * Register your major, and accept a dynamic number + */ + result = register_chrdev(complete_major, "complete", &complete_fops); + if (result < 0) + return result; + if (complete_major == 0) + complete_major = result; /* dynamic */ + return 0; +} + +void complete_cleanup(void) +{ + unregister_chrdev(complete_major, "complete"); +} + +module_init(complete_init); +module_exit(complete_cleanup); + diff --git a/misc-modules/faulty.c b/misc-modules/faulty.c new file mode 100644 index 0000000..48c1850 --- /dev/null +++ b/misc-modules/faulty.c @@ -0,0 +1,89 @@ +/* + * faulty.c -- a module which generates an oops when read + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: faulty.c,v 1.3 2004/09/26 07:02:43 gregkh Exp $ + */ + + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> + +#include <linux/kernel.h> /* printk() */ +#include <linux/fs.h> /* everything... */ +#include <linux/types.h> /* size_t */ +#include <asm/uaccess.h> + +MODULE_LICENSE("Dual BSD/GPL"); + + +int faulty_major = 0; + +ssize_t faulty_read(struct file *filp, char __user *buf, + size_t count, loff_t *pos) +{ + int ret; + char stack_buf[4]; + + /* Let's try a buffer overflow */ + memset(stack_buf, 0xff, 20); + if (count > 4) + count = 4; /* copy 4 bytes to the user */ + ret = copy_to_user(buf, stack_buf, count); + if (!ret) + return count; + return ret; +} + +ssize_t faulty_write (struct file *filp, const char __user *buf, size_t count, + loff_t *pos) +{ + /* make a simple fault by dereferencing a NULL pointer */ + *(int *)0 = 0; + return 0; +} + + + +struct file_operations faulty_fops = { + .read = faulty_read, + .write = faulty_write, + .owner = THIS_MODULE +}; + + +int faulty_init(void) +{ + int result; + + /* + * Register your major, and accept a dynamic number + */ + result = register_chrdev(faulty_major, "faulty", &faulty_fops); + if (result < 0) + return result; + if (faulty_major == 0) + faulty_major = result; /* dynamic */ + + return 0; +} + +void faulty_cleanup(void) +{ + unregister_chrdev(faulty_major, "faulty"); +} + +module_init(faulty_init); +module_exit(faulty_cleanup); + diff --git a/misc-modules/hello.c b/misc-modules/hello.c new file mode 100644 index 0000000..85cd6d0 --- /dev/null +++ b/misc-modules/hello.c @@ -0,0 +1,20 @@ +/* + * $Id: hello.c,v 1.5 2004/10/26 03:32:21 corbet Exp $ + */ +#include <linux/init.h> +#include <linux/module.h> +MODULE_LICENSE("Dual BSD/GPL"); + +static int hello_init(void) +{ + printk(KERN_ALERT "Hello, world\n"); + return 0; +} + +static void hello_exit(void) +{ + printk(KERN_ALERT "Goodbye, cruel world\n"); +} + +module_init(hello_init); +module_exit(hello_exit); diff --git a/misc-modules/hellop.c b/misc-modules/hellop.c new file mode 100644 index 0000000..88eaa67 --- /dev/null +++ b/misc-modules/hellop.c @@ -0,0 +1,40 @@ +/* + * $Id: hellop.c,v 1.4 2004/09/26 07:02:43 gregkh Exp $ + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> + +MODULE_LICENSE("Dual BSD/GPL"); + +/* + * These lines, although not shown in the book, + * are needed to make hello.c run properly even when + * your kernel has version support enabled + */ + + +/* + * A couple of parameters that can be passed in: how many times we say + * hello, and to whom. + */ +static char *whom = "world"; +static int howmany = 1; +module_param(howmany, int, S_IRUGO); +module_param(whom, charp, S_IRUGO); + +static int hello_init(void) +{ + int i; + for (i = 0; i < howmany; i++) + printk(KERN_ALERT "(%d) Hello, %s\n", i, whom); + return 0; +} + +static void hello_exit(void) +{ + printk(KERN_ALERT "Goodbye, cruel world\n"); +} + +module_init(hello_init); +module_exit(hello_exit); diff --git a/misc-modules/jiq.c b/misc-modules/jiq.c new file mode 100644 index 0000000..dc7da28 --- /dev/null +++ b/misc-modules/jiq.c @@ -0,0 +1,264 @@ +/* + * jiq.c -- the just-in-queue module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: jiq.c,v 1.7 2004/09/26 07:02:43 gregkh Exp $ + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/fs.h> /* everything... */ +#include <linux/proc_fs.h> +#include <linux/errno.h> /* error codes */ +#include <linux/workqueue.h> +#include <linux/preempt.h> +#include <linux/interrupt.h> /* tasklets */ + +MODULE_LICENSE("Dual BSD/GPL"); + +/* + * The delay for the delayed workqueue timer file. + */ +static long delay = 1; +module_param(delay, long, 0); + + +/* + * This module is a silly one: it only embeds short code fragments + * that show how enqueued tasks `feel' the environment + */ + +#define LIMIT (PAGE_SIZE-128) /* don't print any more after this size */ + +/* + * Print information about the current environment. This is called from + * within the task queues. If the limit is reched, awake the reading + * process. + */ +static DECLARE_WAIT_QUEUE_HEAD (jiq_wait); + + +static struct work_struct jiq_work; + + + +/* + * Keep track of info we need between task queue runs. + */ +static struct clientdata { + int len; + char *buf; + unsigned long jiffies; + long delay; +} jiq_data; + +#define SCHEDULER_QUEUE ((task_queue *) 1) + + + +static void jiq_print_tasklet(unsigned long); +static DECLARE_TASKLET(jiq_tasklet, jiq_print_tasklet, (unsigned long)&jiq_data); + + +/* + * Do the printing; return non-zero if the task should be rescheduled. + */ +static int jiq_print(void *ptr) +{ + struct clientdata *data = ptr; + int len = data->len; + char *buf = data->buf; + unsigned long j = jiffies; + + if (len > LIMIT) { + wake_up_interruptible(&jiq_wait); + return 0; + } + + if (len == 0) + len = sprintf(buf," time delta preempt pid cpu command\n"); + else + len =0; + + /* intr_count is only exported since 1.3.5, but 1.99.4 is needed anyways */ + len += sprintf(buf+len, "%9li %4li %3i %5i %3i %s\n", + j, j - data->jiffies, + preempt_count(), current->pid, smp_processor_id(), + current->comm); + + data->len += len; + data->buf += len; + data->jiffies = j; + return 1; +} + + +/* + * Call jiq_print from a work queue + */ +static void jiq_print_wq(void *ptr) +{ + struct clientdata *data = (struct clientdata *) ptr; + + if (! jiq_print (ptr)) + return; + + if (data->delay) + schedule_delayed_work(&jiq_work, data->delay); + else + schedule_work(&jiq_work); +} + + + +static int jiq_read_wq(char *buf, char **start, off_t offset, + int len, int *eof, void *data) +{ + DEFINE_WAIT(wait); + + jiq_data.len = 0; /* nothing printed, yet */ + jiq_data.buf = buf; /* print in this place */ + jiq_data.jiffies = jiffies; /* initial time */ + jiq_data.delay = 0; + + prepare_to_wait(&jiq_wait, &wait, TASK_INTERRUPTIBLE); + schedule_work(&jiq_work); + schedule(); + finish_wait(&jiq_wait, &wait); + + *eof = 1; + return jiq_data.len; +} + + +static int jiq_read_wq_delayed(char *buf, char **start, off_t offset, + int len, int *eof, void *data) +{ + DEFINE_WAIT(wait); + + jiq_data.len = 0; /* nothing printed, yet */ + jiq_data.buf = buf; /* print in this place */ + jiq_data.jiffies = jiffies; /* initial time */ + jiq_data.delay = delay; + + prepare_to_wait(&jiq_wait, &wait, TASK_INTERRUPTIBLE); + schedule_delayed_work(&jiq_work, delay); + schedule(); + finish_wait(&jiq_wait, &wait); + + *eof = 1; + return jiq_data.len; +} + + + + +/* + * Call jiq_print from a tasklet + */ +static void jiq_print_tasklet(unsigned long ptr) +{ + if (jiq_print ((void *) ptr)) + tasklet_schedule (&jiq_tasklet); +} + + + +static int jiq_read_tasklet(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + jiq_data.len = 0; /* nothing printed, yet */ + jiq_data.buf = buf; /* print in this place */ + jiq_data.jiffies = jiffies; /* initial time */ + + tasklet_schedule(&jiq_tasklet); + interruptible_sleep_on(&jiq_wait); /* sleep till completion */ + + *eof = 1; + return jiq_data.len; +} + + + + +/* + * This one, instead, tests out the timers. + */ + +static struct timer_list jiq_timer; + +static void jiq_timedout(unsigned long ptr) +{ + jiq_print((void *)ptr); /* print a line */ + wake_up_interruptible(&jiq_wait); /* awake the process */ +} + + +static int jiq_read_run_timer(char *buf, char **start, off_t offset, + int len, int *eof, void *data) +{ + + jiq_data.len = 0; /* prepare the argument for jiq_print() */ + jiq_data.buf = buf; + jiq_data.jiffies = jiffies; + + init_timer(&jiq_timer); /* init the timer structure */ + jiq_timer.function = jiq_timedout; + jiq_timer.data = (unsigned long)&jiq_data; + jiq_timer.expires = jiffies + HZ; /* one second */ + + jiq_print(&jiq_data); /* print and go to sleep */ + add_timer(&jiq_timer); + interruptible_sleep_on(&jiq_wait); /* RACE */ + del_timer_sync(&jiq_timer); /* in case a signal woke us up */ + + *eof = 1; + return jiq_data.len; +} + + + +/* + * the init/clean material + */ + +static int jiq_init(void) +{ + + /* this line is in jiq_init() */ + INIT_WORK(&jiq_work, jiq_print_wq, &jiq_data); + + create_proc_read_entry("jiqwq", 0, NULL, jiq_read_wq, NULL); + create_proc_read_entry("jiqwqdelay", 0, NULL, jiq_read_wq_delayed, NULL); + create_proc_read_entry("jitimer", 0, NULL, jiq_read_run_timer, NULL); + create_proc_read_entry("jiqtasklet", 0, NULL, jiq_read_tasklet, NULL); + + return 0; /* succeed */ +} + +static void jiq_cleanup(void) +{ + remove_proc_entry("jiqwq", NULL); + remove_proc_entry("jiqwqdelay", NULL); + remove_proc_entry("jitimer", NULL); + remove_proc_entry("jiqtasklet", NULL); +} + + +module_init(jiq_init); +module_exit(jiq_cleanup); diff --git a/misc-modules/jit.c b/misc-modules/jit.c new file mode 100644 index 0000000..62978b0 --- /dev/null +++ b/misc-modules/jit.c @@ -0,0 +1,292 @@ +/* + * jit.c -- the just-in-time module + * + * Copyright (C) 2001,2003 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001,2003 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: jit.c,v 1.16 2004/09/26 07:02:43 gregkh Exp $ + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include <linux/time.h> +#include <linux/timer.h> +#include <linux/kernel.h> +#include <linux/proc_fs.h> +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> + +#include <asm/hardirq.h> +/* + * This module is a silly one: it only embeds short code fragments + * that show how time delays can be handled in the kernel. + */ + +int delay = HZ; /* the default delay, expressed in jiffies */ + +module_param(delay, int, 0); + +MODULE_AUTHOR("Alessandro Rubini"); +MODULE_LICENSE("Dual BSD/GPL"); + +/* use these as data pointers, to implement four files in one function */ +enum jit_files { + JIT_BUSY, + JIT_SCHED, + JIT_QUEUE, + JIT_SCHEDTO +}; + +/* + * This function prints one line of data, after sleeping one second. + * It can sleep in different ways, according to the data pointer + */ +int jit_fn(char *buf, char **start, off_t offset, + int len, int *eof, void *data) +{ + unsigned long j0, j1; /* jiffies */ + wait_queue_head_t wait; + + init_waitqueue_head (&wait); + j0 = jiffies; + j1 = j0 + delay; + + switch((long)data) { + case JIT_BUSY: + while (time_before(jiffies, j1)) + cpu_relax(); + break; + case JIT_SCHED: + while (time_before(jiffies, j1)) { + schedule(); + } + break; + case JIT_QUEUE: + wait_event_interruptible_timeout(wait, 0, delay); + break; + case JIT_SCHEDTO: + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout (delay); + break; + } + j1 = jiffies; /* actual value after we delayed */ + + len = sprintf(buf, "%9li %9li\n", j0, j1); + *start = buf; + return len; +} + +/* + * This file, on the other hand, returns the current time forever + */ +int jit_currentime(char *buf, char **start, off_t offset, + int len, int *eof, void *data) +{ + struct timeval tv1; + struct timespec tv2; + unsigned long j1; + u64 j2; + + /* get them four */ + j1 = jiffies; + j2 = get_jiffies_64(); + do_gettimeofday(&tv1); + tv2 = current_kernel_time(); + + /* print */ + len=0; + len += sprintf(buf,"0x%08lx 0x%016Lx %10i.%06i\n" + "%40i.%09i\n", + j1, j2, + (int) tv1.tv_sec, (int) tv1.tv_usec, + (int) tv2.tv_sec, (int) tv2.tv_nsec); + *start = buf; + return len; +} + +/* + * The timer example follows + */ + +int tdelay = 10; +module_param(tdelay, int, 0); + +/* This data structure used as "data" for the timer and tasklet functions */ +struct jit_data { + struct timer_list timer; + struct tasklet_struct tlet; + int hi; /* tasklet or tasklet_hi */ + wait_queue_head_t wait; + unsigned long prevjiffies; + unsigned char *buf; + int loops; +}; +#define JIT_ASYNC_LOOPS 5 + +void jit_timer_fn(unsigned long arg) +{ + struct jit_data *data = (struct jit_data *)arg; + unsigned long j = jiffies; + data->buf += sprintf(data->buf, "%9li %3li %i %6i %i %s\n", + j, j - data->prevjiffies, in_interrupt() ? 1 : 0, + current->pid, smp_processor_id(), current->comm); + + if (--data->loops) { + data->timer.expires += tdelay; + data->prevjiffies = j; + add_timer(&data->timer); + } else { + wake_up_interruptible(&data->wait); + } +} + +/* the /proc function: allocate everything to allow concurrency */ +int jit_timer(char *buf, char **start, off_t offset, + int len, int *eof, void *unused_data) +{ + struct jit_data *data; + char *buf2 = buf; + unsigned long j = jiffies; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + init_timer(&data->timer); + init_waitqueue_head (&data->wait); + + /* write the first lines in the buffer */ + buf2 += sprintf(buf2, " time delta inirq pid cpu command\n"); + buf2 += sprintf(buf2, "%9li %3li %i %6i %i %s\n", + j, 0L, in_interrupt() ? 1 : 0, + current->pid, smp_processor_id(), current->comm); + + /* fill the data for our timer function */ + data->prevjiffies = j; + data->buf = buf2; + data->loops = JIT_ASYNC_LOOPS; + + /* register the timer */ + data->timer.data = (unsigned long)data; + data->timer.function = jit_timer_fn; + data->timer.expires = j + tdelay; /* parameter */ + add_timer(&data->timer); + + /* wait for the buffer to fill */ + wait_event_interruptible(data->wait, !data->loops); + if (signal_pending(current)) + return -ERESTARTSYS; + buf2 = data->buf; + kfree(data); + *eof = 1; + return buf2 - buf; +} + +void jit_tasklet_fn(unsigned long arg) +{ + struct jit_data *data = (struct jit_data *)arg; + unsigned long j = jiffies; + data->buf += sprintf(data->buf, "%9li %3li %i %6i %i %s\n", + j, j - data->prevjiffies, in_interrupt() ? 1 : 0, + current->pid, smp_processor_id(), current->comm); + + if (--data->loops) { + data->prevjiffies = j; + if (data->hi) + tasklet_hi_schedule(&data->tlet); + else + tasklet_schedule(&data->tlet); + } else { + wake_up_interruptible(&data->wait); + } +} + +/* the /proc function: allocate everything to allow concurrency */ +int jit_tasklet(char *buf, char **start, off_t offset, + int len, int *eof, void *arg) +{ + struct jit_data *data; + char *buf2 = buf; + unsigned long j = jiffies; + long hi = (long)arg; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + init_waitqueue_head (&data->wait); + + /* write the first lines in the buffer */ + buf2 += sprintf(buf2, " time delta inirq pid cpu command\n"); + buf2 += sprintf(buf2, "%9li %3li %i %6i %i %s\n", + j, 0L, in_interrupt() ? 1 : 0, + current->pid, smp_processor_id(), current->comm); + + /* fill the data for our tasklet function */ + data->prevjiffies = j; + data->buf = buf2; + data->loops = JIT_ASYNC_LOOPS; + + /* register the tasklet */ + tasklet_init(&data->tlet, jit_tasklet_fn, (unsigned long)data); + data->hi = hi; + if (hi) + tasklet_hi_schedule(&data->tlet); + else + tasklet_schedule(&data->tlet); + + /* wait for the buffer to fill */ + wait_event_interruptible(data->wait, !data->loops); + + if (signal_pending(current)) + return -ERESTARTSYS; + buf2 = data->buf; + kfree(data); + *eof = 1; + return buf2 - buf; +} + + + +int __init jit_init(void) +{ + create_proc_read_entry("currentime", 0, NULL, jit_currentime, NULL); + create_proc_read_entry("jitbusy", 0, NULL, jit_fn, (void *)JIT_BUSY); + create_proc_read_entry("jitsched",0, NULL, jit_fn, (void *)JIT_SCHED); + create_proc_read_entry("jitqueue",0, NULL, jit_fn, (void *)JIT_QUEUE); + create_proc_read_entry("jitschedto", 0, NULL, jit_fn, (void *)JIT_SCHEDTO); + + create_proc_read_entry("jitimer", 0, NULL, jit_timer, NULL); + create_proc_read_entry("jitasklet", 0, NULL, jit_tasklet, NULL); + create_proc_read_entry("jitasklethi", 0, NULL, jit_tasklet, (void *)1); + + return 0; /* success */ +} + +void __exit jit_cleanup(void) +{ + remove_proc_entry("currentime", NULL); + remove_proc_entry("jitbusy", NULL); + remove_proc_entry("jitsched", NULL); + remove_proc_entry("jitqueue", NULL); + remove_proc_entry("jitschedto", NULL); + + remove_proc_entry("jitimer", NULL); + remove_proc_entry("jitasklet", NULL); + remove_proc_entry("jitasklethi", NULL); +} + +module_init(jit_init); +module_exit(jit_cleanup); diff --git a/misc-modules/kdataalign.c b/misc-modules/kdataalign.c new file mode 100644 index 0000000..92d8ace --- /dev/null +++ b/misc-modules/kdataalign.c @@ -0,0 +1,69 @@ +/* + * kdatasize.c -- print the size of common data items from kernel space + * This runs with any Linux kernel (not any Unix, because of <linux/types.h>) + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/utsname.h> +#include <linux/errno.h> + +/* + * Define several data structures, all of them start with a lone char + * in order to present an unaligned offset for the next field + */ +struct c {char c; char t;} c; +struct s {char c; short t;} s; +struct i {char c; int t;} i; +struct l {char c; long t;} l; +struct ll {char c; long long t;} ll; +struct p {char c; void * t;} p; +struct u1b {char c; __u8 t;} u1b; +struct u2b {char c; __u16 t;} u2b; +struct u4b {char c; __u32 t;} u4b; +struct u8b {char c; __u64 t;} u8b; + +static void data_cleanup(void) +{ + /* never called */ +} + +static int data_init(void) +{ + /* print information and return an error */ + printk("arch Align: char short int long ptr long-long " + " u8 u16 u32 u64\n"); + printk("%-12s %3i %3i %3i %3i %3i %3i " + "%3i %3i %3i %3i\n", + system_utsname.machine, + /* note that gcc can subtract void * values, but it's not ansi */ + (int)((void *)(&c.t) - (void *)&c), + (int)((void *)(&s.t) - (void *)&s), + (int)((void *)(&i.t) - (void *)&i), + (int)((void *)(&l.t) - (void *)&l), + (int)((void *)(&p.t) - (void *)&p), + (int)((void *)(&ll.t) - (void *)&ll), + (int)((void *)(&u1b.t) - (void *)&u1b), + (int)((void *)(&u2b.t) - (void *)&u2b), + (int)((void *)(&u4b.t) - (void *)&u4b), + (int)((void *)(&u8b.t) - (void *)&u8b)); + return -ENODEV; +} + +module_init(data_init); +module_exit(data_cleanup); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/misc-modules/kdatasize.c b/misc-modules/kdatasize.c new file mode 100644 index 0000000..4ac8895 --- /dev/null +++ b/misc-modules/kdatasize.c @@ -0,0 +1,48 @@ +/* + * kdatasize.c -- print the size of common data items from kernel space + * This runs with any Linux kernel (not any Unix, because of <linux/types.h>) + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + */ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/utsname.h> +#include <linux/errno.h> + +static void data_cleanup(void) +{ + /* never called */ +} + +int data_init(void) +{ + /* print information and return an error */ + printk("arch Size: char short int long ptr long-long " + " u8 u16 u32 u64\n"); + printk("%-12s %3i %3i %3i %3i %3i %3i " + "%3i %3i %3i %3i\n", + system_utsname.machine, + (int)sizeof(char), (int)sizeof(short), (int)sizeof(int), + (int)sizeof(long), + (int)sizeof(void *), (int)sizeof(long long), (int)sizeof(__u8), + (int)sizeof(__u16), (int)sizeof(__u32), (int)sizeof(__u64)); + return -ENODEV; +} + +module_init(data_init); +module_exit(data_cleanup); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/misc-modules/seq.c b/misc-modules/seq.c new file mode 100644 index 0000000..59026a5 --- /dev/null +++ b/misc-modules/seq.c @@ -0,0 +1,109 @@ +/* + * Simple demonstration of the seq_file interface. + * + * $Id: seq.c,v 1.3 2004/09/26 07:02:43 gregkh Exp $ + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/proc_fs.h> +#include <linux/fs.h> +#include <linux/seq_file.h> +#include <linux/slab.h> + + +MODULE_AUTHOR("Jonathan Corbet"); +MODULE_LICENSE("Dual BSD/GPL"); + + + +/* + * The sequence iterator functions. The position as seen by the + * filesystem is just the count that we return. + */ +static void *ct_seq_start(struct seq_file *s, loff_t *pos) +{ + loff_t *spos = kmalloc(sizeof(loff_t), GFP_KERNEL); + if (!spos) + return NULL; + *spos = *pos; + return spos; +} + +static void *ct_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + loff_t *spos = (loff_t *) v; + *pos = ++(*spos); + return spos; +} + +static void ct_seq_stop(struct seq_file *s, void *v) +{ + kfree (v); +} + +/* + * The show function. + */ +static int ct_seq_show(struct seq_file *s, void *v) +{ + loff_t *spos = (loff_t *) v; + seq_printf(s, "%Ld\n", *spos); + return 0; +} + +/* + * Tie them all together into a set of seq_operations. + */ +static struct seq_operations ct_seq_ops = { + .start = ct_seq_start, + .next = ct_seq_next, + .stop = ct_seq_stop, + .show = ct_seq_show +}; + + +/* + * Time to set up the file operations for our /proc file. In this case, + * all we need is an open function which sets up the sequence ops. + */ + +static int ct_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &ct_seq_ops); +}; + +/* + * The file operations structure contains our open function along with + * set of the canned seq_ ops. + */ +static struct file_operations ct_file_ops = { + .owner = THIS_MODULE, + .open = ct_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release +}; + + +/* + * Module setup and teardown. + */ + +static int ct_init(void) +{ + struct proc_dir_entry *entry; + + entry = create_proc_entry("sequence", 0, NULL); + if (entry) + entry->proc_fops = &ct_file_ops; + return 0; +} + +static void ct_exit(void) +{ + remove_proc_entry("sequence", NULL); +} + +module_init(ct_init); +module_exit(ct_exit); diff --git a/misc-modules/silly.c b/misc-modules/silly.c new file mode 100644 index 0000000..3b1f893 --- /dev/null +++ b/misc-modules/silly.c @@ -0,0 +1,294 @@ +/* + * silly.c -- Simple Tool for Unloading and Printing ISA Data + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: silly.c,v 1.3 2004/09/26 07:02:43 gregkh Exp $ + */ + +/* =========================> BIG FAT WARNING: + * This will only work on architectures with an ISA memory range. + * It won't work on other computers. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/moduleparam.h> +#include <linux/version.h> + +#include <linux/sched.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/poll.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +int silly_major = 0; +module_param(silly_major, int, 0); +MODULE_AUTHOR("Alessandro Rubini"); +MODULE_LICENSE("Dual BSD/GPL"); + +/* + * The devices access the 640k-1M memory. + * minor 0 uses ioread8/iowrite8 + * minor 1 uses ioread16/iowrite16 + * minor 2 uses ioread32/iowrite32 + * minor 3 uses memcpy_fromio()/memcpy_toio() + */ + +/* + * Here's our address range, and a place to store the ioremap'd base. + */ +#define ISA_BASE 0xA0000 +#define ISA_MAX 0x100000 /* for general memory access */ + +#define VIDEO_MAX 0xC0000 /* for vga access */ +#define VGA_BASE 0xb8000 +static void __iomem *io_base; + + + +int silly_open(struct inode *inode, struct file *filp) +{ + return 0; +} + +int silly_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +enum silly_modes {M_8=0, M_16, M_32, M_memcpy}; + +ssize_t silly_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) +{ + int retval; + int mode = iminor(filp->f_dentry->d_inode); + void __iomem *add; + unsigned long isa_addr = ISA_BASE + *f_pos; + unsigned char *kbuf, *ptr; + + if (isa_addr + count > ISA_MAX) /* range: 0xA0000-0x100000 */ + count = ISA_MAX - isa_addr; + + /* + * too big an f_pos (caused by a malicious lseek()) + * would result in a negative count + */ + if (count < 0) + return 0; + + kbuf = kmalloc(count, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + ptr = kbuf; + retval = count; + /* + * Convert our address into our remapped area. + */ + add = (void __iomem *)(io_base + (isa_addr - ISA_BASE)); + /* + * kbuf is aligned, but the reads might not. In order not to + * drive me mad with unaligned leading and trailing bytes, + * I downgrade the `mode' if unaligned xfers are requested. + */ + + if (mode == M_32 && ((isa_addr | count) & 3)) + mode = M_16; + if (mode == M_16 && ((isa_addr | count) & 1)) + mode = M_8; + + switch(mode) { + case M_32: + while (count >= 4) { + *(u32 *)ptr = ioread32(add); + add += 4; + count -= 4; + ptr += 4; + } + break; + + case M_16: + while (count >= 2) { + *(u16 *)ptr = ioread16(add); + add+=2; + count-=2; + ptr+=2; + } + break; + + case M_8: + while (count) { + *ptr = ioread8(add); + add++; + count--; + ptr++; + } + break; + + case M_memcpy: + memcpy_fromio(ptr, add, count); + break; + + default: + return -EINVAL; + } + if ((retval > 0) && copy_to_user(buf, kbuf, retval)) + retval = -EFAULT; + kfree(kbuf); + *f_pos += retval; + return retval; +} + + +ssize_t silly_write(struct file *filp, const char __user *buf, size_t count, + loff_t *f_pos) +{ + int retval; + int mode = iminor(filp->f_dentry->d_inode); + unsigned long isa_addr = ISA_BASE + *f_pos; + unsigned char *kbuf, *ptr; + void __iomem *add; + + /* + * Writing is dangerous. + * Allow root-only, independently of device permissions + */ + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + if (isa_addr + count > ISA_MAX) /* range: 0xA0000-0x100000 */ + count = ISA_MAX - isa_addr; + + /* + * too big an f_pos (caused by a malicious lseek()) + * results in a negative count + */ + if (count < 0) + return 0; + + kbuf = kmalloc(count, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + ptr = kbuf; + retval=count; + + /* + * kbuf is aligned, but the writes might not. In order not to + * drive me mad with unaligned leading and trailing bytes, + * I downgrade the `mode' if unaligned xfers are requested. + */ + + if (mode == M_32 && ((isa_addr | count) & 3)) + mode = M_16; + if (mode == M_16 && ((isa_addr | count) & 1)) + mode = M_8; + + if (copy_from_user(kbuf, buf, count)) { + kfree(kbuf); + return -EFAULT; + } + ptr = kbuf; + + /* + * Switch over to our remapped address space. + */ + add = (void __iomem *)(io_base + (isa_addr - ISA_BASE)); + + switch(mode) { + case M_32: + while (count >= 4) { + iowrite8(*(u32 *)ptr, add); + add += 4; + count -= 4; + ptr += 4; + } + break; + + case M_16: + while (count >= 2) { + iowrite8(*(u16 *)ptr, add); + add += 2; + count -= 2; + ptr += 2; + } + break; + + case M_8: + while (count) { + iowrite8(*ptr, add); + add++; + count--; + ptr++; + } + break; + + case M_memcpy: + memcpy_toio(add, ptr, count); + break; + + default: + return -EINVAL; + } + *f_pos += retval; + kfree(kbuf); + return retval; +} + + +unsigned int silly_poll(struct file *filp, poll_table *wait) +{ + return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM; +} + + +struct file_operations silly_fops = { + .read = silly_read, + .write = silly_write, + .poll = silly_poll, + .open = silly_open, + .release = silly_release, + .owner = THIS_MODULE +}; + +int silly_init(void) +{ + int result = register_chrdev(silly_major, "silly", &silly_fops); + if (result < 0) { + printk(KERN_INFO "silly: can't get major number\n"); + return result; + } + if (silly_major == 0) + silly_major = result; /* dynamic */ + /* + * Set up our I/O range. + */ + + /* this line appears in silly_init */ + io_base = ioremap(ISA_BASE, ISA_MAX - ISA_BASE); + return 0; +} + +void silly_cleanup(void) +{ + iounmap(io_base); + unregister_chrdev(silly_major, "silly"); +} + + +module_init(silly_init); +module_exit(silly_cleanup); diff --git a/misc-modules/sleepy.c b/misc-modules/sleepy.c new file mode 100644 index 0000000..33c7fc3 --- /dev/null +++ b/misc-modules/sleepy.c @@ -0,0 +1,84 @@ +/* + * sleepy.c -- the writers awake the readers + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: sleepy.c,v 1.7 2004/09/26 07:02:43 gregkh Exp $ + */ + +#include <linux/module.h> +#include <linux/init.h> + +#include <linux/sched.h> /* current and everything */ +#include <linux/kernel.h> /* printk() */ +#include <linux/fs.h> /* everything... */ +#include <linux/types.h> /* size_t */ +#include <linux/wait.h> + +MODULE_LICENSE("Dual BSD/GPL"); + +static int sleepy_major = 0; + +static DECLARE_WAIT_QUEUE_HEAD(wq); +static int flag = 0; + +ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos) +{ + printk(KERN_DEBUG "process %i (%s) going to sleep\n", + current->pid, current->comm); + wait_event_interruptible(wq, flag != 0); + flag = 0; + printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm); + return 0; /* EOF */ +} + +ssize_t sleepy_write (struct file *filp, const char __user *buf, size_t count, + loff_t *pos) +{ + printk(KERN_DEBUG "process %i (%s) awakening the readers...\n", + current->pid, current->comm); + flag = 1; + wake_up_interruptible(&wq); + return count; /* succeed, to avoid retrial */ +} + + +struct file_operations sleepy_fops = { + .owner = THIS_MODULE, + .read = sleepy_read, + .write = sleepy_write, +}; + + +int sleepy_init(void) +{ + int result; + + /* + * Register your major, and accept a dynamic number + */ + result = register_chrdev(sleepy_major, "sleepy", &sleepy_fops); + if (result < 0) + return result; + if (sleepy_major == 0) + sleepy_major = result; /* dynamic */ + return 0; +} + +void sleepy_cleanup(void) +{ + unregister_chrdev(sleepy_major, "sleepy"); +} + +module_init(sleepy_init); +module_exit(sleepy_cleanup); + diff --git a/misc-progs/Makefile b/misc-progs/Makefile new file mode 100644 index 0000000..759ddec --- /dev/null +++ b/misc-progs/Makefile @@ -0,0 +1,13 @@ + +FILES = nbtest load50 mapcmp polltest mapper setlevel setconsole inp outp \ + datasize dataalign netifdebug + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +INCLUDEDIR = $(KERNELDIR)/include +CFLAGS = -O2 -fomit-frame-pointer -Wall -I$(INCLUDEDIR) + +all: $(FILES) + +clean: + rm -f $(FILES) *~ core + diff --git a/misc-progs/asynctest.c b/misc-progs/asynctest.c new file mode 100644 index 0000000..8dcb458 --- /dev/null +++ b/misc-progs/asynctest.c @@ -0,0 +1,57 @@ +/* + * asynctest.c: use async notification to read stdin + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> + +int gotdata=0; +void sighandler(int signo) +{ + if (signo==SIGIO) + gotdata++; + return; +} + +char buffer[4096]; + +int main(int argc, char **argv) +{ + int count; + struct sigaction action; + + memset(&action, 0, sizeof(action)); + action.sa_handler = sighandler; + action.sa_flags = 0; + + sigaction(SIGIO, &action, NULL); + + fcntl(STDIN_FILENO, F_SETOWN, getpid()); + fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | FASYNC); + + while(1) { + /* this only returns if a signal arrives */ + sleep(86400); /* one day */ + if (!gotdata) + continue; + count=read(0, buffer, 4096); + /* buggy: if avail data is more than 4kbytes... */ + write(1,buffer,count); + gotdata=0; + } +} diff --git a/misc-progs/dataalign.c b/misc-progs/dataalign.c new file mode 100644 index 0000000..1042ab7 --- /dev/null +++ b/misc-progs/dataalign.c @@ -0,0 +1,58 @@ +/* + * dataalign.c -- show alignment needs + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * This runs with any Linux kernel (not any Unix, because of <linux/types.h>) + */ +#include <stdio.h> +#include <sys/utsname.h> +#include <linux/types.h> + +/* + * Define several data structures, all of them start with a lone char + * in order to present an unaligned offset for the next field + */ +struct c {char c; char t;} c; +struct s {char c; short t;} s; +struct i {char c; int t;} i; +struct l {char c; long t;} l; +struct ll {char c; long long t;} ll; +struct p {char c; void * t;} p; +struct u1b {char c; __u8 t;} u1b; +struct u2b {char c; __u16 t;} u2b; +struct u4b {char c; __u32 t;} u4b; +struct u8b {char c; __u64 t;} u8b; + +int main(int argc, char **argv) +{ + struct utsname name; + + uname(&name); /* never fails :) */ + printf("arch Align: char short int long ptr long-long " + " u8 u16 u32 u64\n"); + printf( "%-12s %3i %3i %3i %3i %3i %3i " + "%3i %3i %3i %3i\n", + name.machine, + /* note that gcc can subtract void * values, but it's not ansi */ + (int)((void *)(&c.t) - (void *)&c), + (int)((void *)(&s.t) - (void *)&s), + (int)((void *)(&i.t) - (void *)&i), + (int)((void *)(&l.t) - (void *)&l), + (int)((void *)(&p.t) - (void *)&p), + (int)((void *)(&ll.t) - (void *)&ll), + (int)((void *)(&u1b.t) - (void *)&u1b), + (int)((void *)(&u2b.t) - (void *)&u2b), + (int)((void *)(&u4b.t) - (void *)&u4b), + (int)((void *)(&u8b.t) - (void *)&u8b)); + return 0; +} diff --git a/misc-progs/datasize.c b/misc-progs/datasize.c new file mode 100644 index 0000000..bf63423 --- /dev/null +++ b/misc-progs/datasize.c @@ -0,0 +1,35 @@ +/* + * datasize.c -- print the size of common data items + * This runs with any Linux kernel (not any Unix, because of <linux/types.h>) + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + */ +#include <stdio.h> +#include <sys/utsname.h> +#include <linux/types.h> + +int main(int argc, char **argv) +{ + struct utsname name; + + uname(&name); /* never fails :) */ + printf("arch Size: char short int long ptr long-long " + " u8 u16 u32 u64\n"); + printf( "%-12s %3i %3i %3i %3i %3i %3i " + "%3i %3i %3i %3i\n", + name.machine, + (int)sizeof(char), (int)sizeof(short), (int)sizeof(int), + (int)sizeof(long), + (int)sizeof(void *), (int)sizeof(long long), (int)sizeof(__u8), + (int)sizeof(__u16), (int)sizeof(__u32), (int)sizeof(__u64)); + return 0; +} diff --git a/misc-progs/gdbline b/misc-progs/gdbline new file mode 100644 index 0000000..d66572e --- /dev/null +++ b/misc-progs/gdbline @@ -0,0 +1,19 @@ +#!/bin/bash +# +# $Id: gdbline,v 1.1 2004/08/02 16:27:55 corbet Exp $ +# +# gdbline module image +# +# Outputs an add-symbol-file line suitable for pasting into gdb to examine +# a loaded module. +# +cd /sys/module/$1/sections +echo -n add-symbol-file $2 `/bin/cat .text` + +for section in .[a-z]* *; do + if [ $section != ".text" ]; then + echo " \\" + echo -n " -s" $section `/bin/cat $section` + fi +done +echo diff --git a/misc-progs/inp.c b/misc-progs/inp.c new file mode 100644 index 0000000..282d570 --- /dev/null +++ b/misc-progs/inp.c @@ -0,0 +1,129 @@ +/* + * inp.c -- read all the ports specified in hex on the command line. + * The program uses the faster ioperm/iopl calls on x86, /dev/port + * on other platforms. The program acts as inb/inw/inl according + * to its own name + * + * Copyright (C) 1998,2000,2001 Alessandro Rubini + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <asm/io.h> /* linux-specific */ + +#ifdef __GLIBC__ +# include <sys/perm.h> +#endif + +#define PORT_FILE "/dev/port" + +char *prgname; + +#ifdef __i386__ +static int read_and_print_one(unsigned int port,int size) +{ + static int iopldone = 0; + + if (port > 1024) { + if (!iopldone && iopl(3)) { + fprintf(stderr, "%s: iopl(): %s\n", prgname, strerror(errno)); + return 1; + } + iopldone++; + } else if (ioperm(port,size,1)) { + fprintf(stderr, "%s: ioperm(%x): %s\n", prgname, + port, strerror(errno)); + return 1; + } + + if (size == 4) + printf("%04x: %08x\n", port, inl(port)); + else if (size == 2) + printf("%04x: %04x\n", port, inw(port)); + else + printf("%04x: %02x\n", port, inb(port)); + return 0; +} +#else /* not i386 */ + +static int read_and_print_one(unsigned int port,int size) +{ + static int fd = -1; + unsigned char b; unsigned short w; unsigned int l; + + if (fd < 0) + fd = open(PORT_FILE, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "%s: %s: %s\n", prgname, PORT_FILE, strerror(errno)); + return 1; + } + lseek(fd, port, SEEK_SET); + + if (size == 4) { + read(fd, &l, 4); + printf("%04x: 0x%08x\n", port, l); + } else if (size == 2) { + read(fd, &w, 2); + printf("%04x: 0x%04x\n", port, w & 0xffff); + } else { + read(fd, &b, 1); + printf("%04x: 0x%02x\n", port, b & 0xff); + } + return 0; +} + +#endif /* i386 */ + + +int main(int argc, char **argv) +{ + unsigned int i, n, port, size, error = 0; + + prgname = argv[0]; + /* find the data size */ + switch (prgname[strlen(prgname)-1]) { + case 'w': size = 2; break; + case 'l': size = 4; break; + case 'b': case 'p': default: + size = 1; + } + + setuid(0); /* if we're setuid, force it on */ + for (i = 1; i < argc; i++) { + if ( sscanf(argv[i], "%x%n", &port, &n) < 1 + || n != strlen(argv[i]) ) { + fprintf(stderr, "%s: argument \"%s\" is not a hex number\n", + argv[0], argv[i]); + error++; continue; + } + if (port & (size-1)) { + fprintf(stderr, "%s: argument \"%s\" is not properly aligned\n", + argv[0], argv[i]); + error++; continue; + } + error += read_and_print_one(port, size); + } + exit (error ? 1 : 0); +} + diff --git a/misc-progs/load50.c b/misc-progs/load50.c new file mode 100644 index 0000000..f9d93a0 --- /dev/null +++ b/misc-progs/load50.c @@ -0,0 +1,38 @@ +/* + * load50.c -- a simple busy-looping tool. + * Obviously, this runs with any kernel and any Unix + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +int main(int argc, char **argv) +{ + int i, load=50; + + if (argc==2) { + load=atoi(argv[1]); + } + printf("Bringing load to %i\n",load); + + for (i=0; i<load; i++) + if (fork()==0) + break; + + while(1) + ; + return 0; +} + diff --git a/misc-progs/mapcmp.c b/misc-progs/mapcmp.c new file mode 100644 index 0000000..8ef8831 --- /dev/null +++ b/misc-progs/mapcmp.c @@ -0,0 +1,87 @@ +/* + * Simple program to compare two mmap'd areas. + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: mapcmp.c,v 1.2 2004/03/05 17:35:41 corbet Exp $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/errno.h> +#include <fcntl.h> + +static char *mapdev (const char *, unsigned long, unsigned long); +#define PAGE_SIZE 4096 + +/* + * memcmp dev1 dev2 offset pages + */ +int main (int argc, char **argv) +{ + unsigned long offset, size, i; + char *addr1, *addr2; +/* + * Sanity check. + */ + if (argc != 5) + { + fprintf (stderr, "Usage: mapcmp dev1 dev2 offset pages\n"); + exit (1); + } +/* + * Map the two devices. + */ + offset = strtoul (argv[3], NULL, 16); + size = atoi (argv[4])*PAGE_SIZE; + printf ("Offset is 0x%lx\n", offset); + addr1 = mapdev (argv[1], offset, size); + addr2 = mapdev (argv[2], offset, size); +/* + * Do the comparison. + */ + printf ("Comparing..."); + fflush (stdout); + for (i = 0; i < size; i++) + if (*addr1++ != *addr2++) + { + printf ("areas differ at byte %ld\n", i); + exit (0); + } + printf ("areas are identical.\n"); + exit (0); +} + + + +static char *mapdev (const char *dev, unsigned long offset, + unsigned long size) +{ + char *addr; + int fd = open (dev, O_RDONLY); + + if (fd < 0) + { + perror (dev); + exit (1); + } + addr = mmap (0, size, PROT_READ, MAP_PRIVATE, fd, offset); + if (addr == MAP_FAILED) + { + perror (dev); + exit (1); + } + printf ("Mapped %s (%lu @ %lx) at %p\n", dev, size, offset, addr); + return (addr); +} diff --git a/misc-progs/mapper.c b/misc-progs/mapper.c new file mode 100644 index 0000000..ae8db09 --- /dev/null +++ b/misc-progs/mapper.c @@ -0,0 +1,71 @@ +/* + * mapper.c -- simple file that mmap()s a file region and prints it + * + * Copyright (C) 1998,2000,2001 Alessandro Rubini + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/mman.h> +#include <errno.h> +#include <limits.h> + +int main(int argc, char **argv) +{ + char *fname; + FILE *f; + unsigned long offset, len; + void *address; + + if (argc !=4 + || sscanf(argv[2],"%li", &offset) != 1 + || sscanf(argv[3],"%li", &len) != 1) { + fprintf(stderr, "%s: Usage \"%s <file> <offset> <len>\"\n", argv[0], + argv[0]); + exit(1); + } + /* the offset might be big (e.g., PCI devices), but conversion trims it */ + if (offset == INT_MAX) { + if (argv[2][1]=='x') + sscanf(argv[2]+2, "%lx", &offset); + else + sscanf(argv[2], "%lu", &offset); + } + + fname=argv[1]; + + if (!(f=fopen(fname,"r"))) { + fprintf(stderr, "%s: %s: %s\n", argv[0], fname, strerror(errno)); + exit(1); + } + + address=mmap(0, len, PROT_READ, MAP_FILE | MAP_PRIVATE, fileno(f), offset); + + if (address == (void *)-1) { + fprintf(stderr,"%s: mmap(): %s\n",argv[0],strerror(errno)); + exit(1); + } + fclose(f); + fprintf(stderr, "mapped \"%s\" from %lu (0x%08lx) to %lu (0x%08lx)\n", + fname, offset, offset, offset+len, offset+len); + + fwrite(address, 1, len, stdout); + return 0; +} + diff --git a/misc-progs/nbtest.c b/misc-progs/nbtest.c new file mode 100644 index 0000000..6a0bd54 --- /dev/null +++ b/misc-progs/nbtest.c @@ -0,0 +1,44 @@ +/* + * nbtest.c: read and write in non-blocking mode + * This should run with any Unix + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + */ + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdlib.h> +#include <errno.h> + +char buffer[4096]; + +int main(int argc, char **argv) +{ + int delay = 1, n, m = 0; + + if (argc > 1) + delay=atoi(argv[1]); + fcntl(0, F_SETFL, fcntl(0,F_GETFL) | O_NONBLOCK); /* stdin */ + fcntl(1, F_SETFL, fcntl(1,F_GETFL) | O_NONBLOCK); /* stdout */ + + while (1) { + n = read(0, buffer, 4096); + if (n >= 0) + m = write(1, buffer, n); + if ((n < 0 || m < 0) && (errno != EAGAIN)) + break; + sleep(delay); + } + perror(n < 0 ? "stdin" : "stdout"); + exit(1); +} diff --git a/misc-progs/netifdebug.c b/misc-progs/netifdebug.c new file mode 100644 index 0000000..6f0ce75 --- /dev/null +++ b/misc-progs/netifdebug.c @@ -0,0 +1,84 @@ +/* + * netifdebug.c -- change the IFF_DEBUG flag of an interface + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <net/if.h> +#include <netinet/in.h> + +int main(int argc, char **argv) +{ + int action = -1, sock; + struct ifreq req; + char *actname; + + if (argc < 2) { + fprintf(stderr,"%s: usage is \"%s <ifname> [<on|off|tell>]\"\n", + argv[0],argv[0]); + exit(1); + } + if (argc==2) + actname="tell"; + else + actname=argv[2]; + + /* a silly raw socket just for ioctl()ling it */ + sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (sock < 0) { + fprintf(stderr, "%s: socket(): %s\n", argv[0],strerror(errno)); + exit(1); + } + + /* retrieve flags */ + strcpy(req.ifr_name, argv[1]); + if ( ioctl(sock, SIOCGIFFLAGS, &req) < 0) { + fprintf(stderr, " %s: ioctl(SIOCGIFFLAGS): %s\n", + argv[0],strerror(errno)); + exit(1); + } + + if (!strcmp(actname,"on") + || !strcmp(actname,"+") + || !strcmp(actname,"1")) + action = IFF_DEBUG; + + if (!strcmp(actname,"off") + || !strcmp(actname,"-") + || !strcmp(actname,"0")) + action = 0; + + if (!strcmp(actname,"tell") + || actname[0]=='t') { + printf("%s: debug is %s\n", argv[1], + req.ifr_flags & IFF_DEBUG ? "on" : "off"); + exit(0); + } + + req.ifr_flags &= ~IFF_DEBUG; + req.ifr_flags |= action; + + if ( ioctl(sock, SIOCSIFFLAGS, &req) < 0) { + fprintf(stderr, " %s: ioctl(SIOCSIFFLAGS): %s\n", + argv[0],strerror(errno)); + exit(1); + } + exit(0); +} diff --git a/misc-progs/outp.c b/misc-progs/outp.c new file mode 100644 index 0000000..219d613 --- /dev/null +++ b/misc-progs/outp.c @@ -0,0 +1,136 @@ +/* + * outp.c -- write all the ports specified in hex on the command line. + * The program uses the faster ioperm/iopl calls on x86, /dev/port + * on other platforms. The program acts as outb/outw/outl according + * to its own name + * + * Copyright (C) 1998,2000,2001 Alessandro Rubini + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <asm/io.h> /* linux-specific */ + +#ifdef __GLIBC__ +# include <sys/perm.h> +#endif + +#define PORT_FILE "/dev/port" + +char *prgname; + +#ifdef __i386__ +static int write_one(unsigned int port, unsigned int val, int size) +{ + static int iopldone = 0; + + if (port > 1024) { + if (!iopldone && iopl(3)) { + fprintf(stderr, "%s: iopl(): %s\n", prgname, strerror(errno)); + return 1; + } + iopldone++; + } else if (ioperm(port,size,1)) { + fprintf(stderr, "%s: ioperm(%x): %s\n", prgname, + port, strerror(errno)); + return 1; + } + + if (size == 4) + outl(val, port); + else if (size == 2) + outw(val&0xffff, port); + else + outb(val&0xff, port); + return 0; +} +#else /* not i386 */ + +static int write_one(unsigned int port, unsigned int val, int size) +{ + static int fd = -1; + unsigned char b; unsigned short w; + + if (fd < 0) + fd = open(PORT_FILE, O_WRONLY); + if (fd < 0) { + fprintf(stderr, "%s: %s: %s\n", prgname, PORT_FILE, strerror(errno)); + return 1; + } + lseek(fd, port, SEEK_SET); + + if (size == 4) { + write(fd, &val, 4); + } else if (size == 2) { + w = val; + write(fd, &w, 2); + } else { + b = val; + write(fd, &b, 1); + } + return 0; +} + +#endif /* i386 */ + +int main(int argc, char **argv) +{ + unsigned int i, n, port, val, size, error = 0; + + prgname = argv[0]; + /* find the data size */ + switch (prgname[strlen(prgname)-1]) { + case 'w': size = 2; break; + case 'l': size = 4; break; + case 'b': case 'p': default: + size = 1; + } + setuid(0); /* if we're setuid, force it on */ + for (i=1;i<argc-1;i++) { + if ( sscanf(argv[i], "%x%n", &port, &n) < 1 + || n != strlen(argv[i]) ) { + fprintf(stderr, "%s: argument \"%s\" is not a hex number\n", + argv[0], argv[i]); + error++; continue; + } + if (port & (size-1)) { + fprintf(stderr, "%s: argument \"%s\" is not properly aligned\n", + argv[0], argv[i]); + error++; continue; + } + if ( sscanf(argv[i+1], "%x%n", &val, &n) < 1 + || n != strlen(argv[i+1]) ) { + fprintf(stderr, "%s: argument \"%s\" is not a hex number\n", + argv[0], argv[i+1]); + error++; continue; + } + if (size < 4 && val > (size == 1 ? 0xff : 0xffff)) { + fprintf(stderr, "%s: argument \"%s\" out of range\n", + argv[0], argv[i+1]); + error++; continue; + } + error += write_one(port, val, size); + } + exit (error ? 1 : 0); +} diff --git a/misc-progs/polltest.c b/misc-progs/polltest.c new file mode 100644 index 0000000..fdc461d --- /dev/null +++ b/misc-progs/polltest.c @@ -0,0 +1,47 @@ +/* + * Test out reading with poll() + * This should run with any Unix + * + * Copyright (C) 2003 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2003 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: polltest.c,v 1.1 2003/02/07 18:01:38 corbet Exp $ + */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/poll.h> +#include <fcntl.h> + +char buffer[4096]; + +int main(int argc, char **argv) +{ + struct pollfd pfd; + int n; + + fcntl(0, F_SETFL, fcntl(0,F_GETFL) | O_NONBLOCK); /* stdin */ + pfd.fd = 0; /* stdin */ + pfd.events = POLLIN; + + while (1) { + n=read(0, buffer, 4096); + if (n >= 0) + write(1, buffer, n); + n = poll(&pfd, 1, -1); + if (n < 0) + break; + } + perror( n<0 ? "stdin" : "stdout"); + exit(1); +} diff --git a/misc-progs/setconsole.c b/misc-progs/setconsole.c new file mode 100644 index 0000000..04bc11d --- /dev/null +++ b/misc-progs/setconsole.c @@ -0,0 +1,42 @@ +/* + * setconsole.c -- choose a console to receive kernel messages + * + * Copyright (C) 1998,2000,2001 Alessandro Rubini + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/ioctl.h> + +int main(int argc, char **argv) +{ + char bytes[2] = {11,0}; /* 11 is the TIOCLINUX cmd number */ + + if (argc==2) bytes[1] = atoi(argv[1]); /* the chosen console */ + else { + fprintf(stderr, "%s: need a single arg\n",argv[0]); exit(1); + } + if (ioctl(STDIN_FILENO, TIOCLINUX, bytes)<0) { /* use stdin */ + fprintf(stderr,"%s: ioctl(stdin, TIOCLINUX): %s\n", + argv[0], strerror(errno)); + exit(1); + } + exit(0); +} diff --git a/misc-progs/setlevel.c b/misc-progs/setlevel.c new file mode 100644 index 0000000..fec666d --- /dev/null +++ b/misc-progs/setlevel.c @@ -0,0 +1,47 @@ +/* + * setlevel.c -- choose a console_loglevel for the kernel + * + * Copyright (C) 1998,2000,2001 Alessandro Rubini + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +/* #include <unistd.h> */ /* conflicting on the alpha */ +#define __LIBRARY__ /* _syscall3 and friends are only available through this */ +#include <linux/unistd.h> + +/* define the system call, to override the library function */ +_syscall3(int, syslog, int, type, char *, bufp, int, len); + +int main(int argc, char **argv) +{ + int level; + + if (argc==2) { + level = atoi(argv[1]); /* the chosen console */ + } else { + fprintf(stderr, "%s: need a single arg\n",argv[0]); exit(1); + } + if (syslog(8,NULL,level) < 0) { + fprintf(stderr,"%s: syslog(setlevel): %s\n", + argv[0],strerror(errno)); + exit(1); + } + exit(0); +} diff --git a/pci/Makefile b/pci/Makefile new file mode 100644 index 0000000..8b6a333 --- /dev/null +++ b/pci/Makefile @@ -0,0 +1,11 @@ +obj-m := pci_skel.o + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +all: + $(MAKE) -C $(KERNELDIR) M=$(PWD) + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + diff --git a/pci/pci_skel.c b/pci/pci_skel.c new file mode 100644 index 0000000..f115669 --- /dev/null +++ b/pci/pci_skel.c @@ -0,0 +1,63 @@ +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/init.h> + + +static struct pci_device_id ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_3), }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, ids); + +static unsigned char skel_get_revision(struct pci_dev *dev) +{ + u8 revision; + + pci_read_config_byte(dev, PCI_REVISION_ID, &revision); + return revision; +} + +static int probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + /* Do probing type stuff here. + * Like calling request_region(); + */ + pci_enable_device(dev); + + if (skel_get_revision(dev) == 0x42) + return -ENODEV; + + + return 0; +} + +static void remove(struct pci_dev *dev) +{ + /* clean up any allocated resources and stuff here. + * like call release_region(); + */ +} + +static struct pci_driver pci_driver = { + .name = "pci_skel", + .id_table = ids, + .probe = probe, + .remove = remove, +}; + +static int __init pci_skel_init(void) +{ + return pci_register_driver(&pci_driver); +} + +static void __exit pci_skel_exit(void) +{ + pci_unregister_driver(&pci_driver); +} + +MODULE_LICENSE("GPL"); + +module_init(pci_skel_init); +module_exit(pci_skel_exit); diff --git a/sbull/Makefile b/sbull/Makefile new file mode 100644 index 0000000..5bb807c --- /dev/null +++ b/sbull/Makefile @@ -0,0 +1,41 @@ +# Comment/uncomment the following line to disable/enable debugging +#DEBUG = y + + +# Add your debugging flag (or not) to CFLAGS +ifeq ($(DEBUG),y) + DEBFLAGS = -O -g -DSBULL_DEBUG # "-O" is needed to expand inlines +else + DEBFLAGS = -O2 +endif + +CFLAGS += $(DEBFLAGS) +CFLAGS += -I.. + +ifneq ($(KERNELRELEASE),) +# call from kernel build system + +obj-m := sbull.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +default: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +endif + + + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/sbull/sbull.c b/sbull/sbull.c new file mode 100644 index 0000000..1a040c3 --- /dev/null +++ b/sbull/sbull.c @@ -0,0 +1,456 @@ +/* + * Sample disk driver, from the beginning. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include <linux/sched.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/slab.h> /* kmalloc() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/timer.h> +#include <linux/types.h> /* size_t */ +#include <linux/fcntl.h> /* O_ACCMODE */ +#include <linux/hdreg.h> /* HDIO_GETGEO */ +#include <linux/kdev_t.h> +#include <linux/vmalloc.h> +#include <linux/genhd.h> +#include <linux/blkdev.h> +#include <linux/buffer_head.h> /* invalidate_bdev */ +#include <linux/bio.h> + +MODULE_LICENSE("Dual BSD/GPL"); + +static int sbull_major = 0; +module_param(sbull_major, int, 0); +static int hardsect_size = 512; +module_param(hardsect_size, int, 0); +static int nsectors = 1024; /* How big the drive is */ +module_param(nsectors, int, 0); +static int ndevices = 4; +module_param(ndevices, int, 0); + +/* + * The different "request modes" we can use. + */ +enum { + RM_SIMPLE = 0, /* The extra-simple request function */ + RM_FULL = 1, /* The full-blown version */ + RM_NOQUEUE = 2, /* Use make_request */ +}; +static int request_mode = RM_SIMPLE; +module_param(request_mode, int, 0); + +/* + * Minor number and partition management. + */ +#define SBULL_MINORS 16 +#define MINOR_SHIFT 4 +#define DEVNUM(kdevnum) (MINOR(kdev_t_to_nr(kdevnum)) >> MINOR_SHIFT + +/* + * We can tweak our hardware sector size, but the kernel talks to us + * in terms of small sectors, always. + */ +#define KERNEL_SECTOR_SIZE 512 + +/* + * After this much idle time, the driver will simulate a media change. + */ +#define INVALIDATE_DELAY 30*HZ + +/* + * The internal representation of our device. + */ +struct sbull_dev { + int size; /* Device size in sectors */ + u8 *data; /* The data array */ + short users; /* How many users */ + short media_change; /* Flag a media change? */ + spinlock_t lock; /* For mutual exclusion */ + struct request_queue *queue; /* The device request queue */ + struct gendisk *gd; /* The gendisk structure */ + struct timer_list timer; /* For simulated media changes */ +}; + +static struct sbull_dev *Devices = NULL; + +/* + * Handle an I/O request. + */ +static void sbull_transfer(struct sbull_dev *dev, unsigned long sector, + unsigned long nsect, char *buffer, int write) +{ + unsigned long offset = sector*KERNEL_SECTOR_SIZE; + unsigned long nbytes = nsect*KERNEL_SECTOR_SIZE; + + if ((offset + nbytes) > dev->size) { + printk (KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes); + return; + } + if (write) + memcpy(dev->data + offset, buffer, nbytes); + else + memcpy(buffer, dev->data + offset, nbytes); +} + +/* + * The simple form of the request function. + */ +static void sbull_request(request_queue_t *q) +{ + struct request *req; + + while ((req = elv_next_request(q)) != NULL) { + struct sbull_dev *dev = req->rq_disk->private_data; + if (! blk_fs_request(req)) { + printk (KERN_NOTICE "Skip non-fs request\n"); + end_request(req, 0); + continue; + } + // printk (KERN_NOTICE "Req dev %d dir %ld sec %ld, nr %d f %lx\n", + // dev - Devices, rq_data_dir(req), + // req->sector, req->current_nr_sectors, + // req->flags); + sbull_transfer(dev, req->sector, req->current_nr_sectors, + req->buffer, rq_data_dir(req)); + end_request(req, 1); + } +} + + +/* + * Transfer a single BIO. + */ +static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio) +{ + int i; + struct bio_vec *bvec; + sector_t sector = bio->bi_sector; + + /* Do each segment independently. */ + bio_for_each_segment(bvec, bio, i) { + char *buffer = __bio_kmap_atomic(bio, i, KM_USER0); + sbull_transfer(dev, sector, bio_cur_sectors(bio), + buffer, bio_data_dir(bio) == WRITE); + sector += bio_cur_sectors(bio); + __bio_kunmap_atomic(bio, KM_USER0); + } + return 0; /* Always "succeed" */ +} + +/* + * Transfer a full request. + */ +static int sbull_xfer_request(struct sbull_dev *dev, struct request *req) +{ + struct bio *bio; + int nsect = 0; + + rq_for_each_bio(bio, req) { + sbull_xfer_bio(dev, bio); + nsect += bio->bi_size/KERNEL_SECTOR_SIZE; + } + return nsect; +} + + + +/* + * Smarter request function that "handles clustering". + */ +static void sbull_full_request(request_queue_t *q) +{ + struct request *req; + int sectors_xferred; + struct sbull_dev *dev = q->queuedata; + + while ((req = elv_next_request(q)) != NULL) { + if (! blk_fs_request(req)) { + printk (KERN_NOTICE "Skip non-fs request\n"); + end_request(req, 0); + continue; + } + sectors_xferred = sbull_xfer_request(dev, req); + if (! end_that_request_first(req, 1, sectors_xferred)) { + blkdev_dequeue_request(req); + end_that_request_last(req); + } + } +} + + + +/* + * The direct make request version. + */ +static int sbull_make_request(request_queue_t *q, struct bio *bio) +{ + struct sbull_dev *dev = q->queuedata; + int status; + + status = sbull_xfer_bio(dev, bio); + bio_endio(bio, bio->bi_size, status); + return 0; +} + + +/* + * Open and close. + */ + +static int sbull_open(struct inode *inode, struct file *filp) +{ + struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data; + + del_timer_sync(&dev->timer); + filp->private_data = dev; + spin_lock(&dev->lock); + if (! dev->users) + check_disk_change(inode->i_bdev); + dev->users++; + spin_unlock(&dev->lock); + return 0; +} + +static int sbull_release(struct inode *inode, struct file *filp) +{ + struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data; + + spin_lock(&dev->lock); + dev->users--; + + if (!dev->users) { + dev->timer.expires = jiffies + INVALIDATE_DELAY; + add_timer(&dev->timer); + } + spin_unlock(&dev->lock); + + return 0; +} + +/* + * Look for a (simulated) media change. + */ +int sbull_media_changed(struct gendisk *gd) +{ + struct sbull_dev *dev = gd->private_data; + + return dev->media_change; +} + +/* + * Revalidate. WE DO NOT TAKE THE LOCK HERE, for fear of deadlocking + * with open. That needs to be reevaluated. + */ +int sbull_revalidate(struct gendisk *gd) +{ + struct sbull_dev *dev = gd->private_data; + + if (dev->media_change) { + dev->media_change = 0; + memset (dev->data, 0, dev->size); + } + return 0; +} + +/* + * The "invalidate" function runs out of the device timer; it sets + * a flag to simulate the removal of the media. + */ +void sbull_invalidate(unsigned long ldev) +{ + struct sbull_dev *dev = (struct sbull_dev *) ldev; + + spin_lock(&dev->lock); + if (dev->users || !dev->data) + printk (KERN_WARNING "sbull: timer sanity check failed\n"); + else + dev->media_change = 1; + spin_unlock(&dev->lock); +} + +/* + * The ioctl() implementation + */ + +int sbull_ioctl (struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + long size; + struct hd_geometry geo; + struct sbull_dev *dev = filp->private_data; + + switch(cmd) { + case HDIO_GETGEO: + /* + * Get geometry: since we are a virtual device, we have to make + * up something plausible. So we claim 16 sectors, four heads, + * and calculate the corresponding number of cylinders. We set the + * start of data at sector four. + */ + size = dev->size*(hardsect_size/KERNEL_SECTOR_SIZE); + geo.cylinders = (size & ~0x3f) >> 6; + geo.heads = 4; + geo.sectors = 16; + geo.start = 4; + if (copy_to_user((void __user *) arg, &geo, sizeof(geo))) + return -EFAULT; + return 0; + } + + return -ENOTTY; /* unknown command */ +} + + + +/* + * The device operations structure. + */ +static struct block_device_operations sbull_ops = { + .owner = THIS_MODULE, + .open = sbull_open, + .release = sbull_release, + .media_changed = sbull_media_changed, + .revalidate_disk = sbull_revalidate, + .ioctl = sbull_ioctl +}; + + +/* + * Set up our internal device. + */ +static void setup_device(struct sbull_dev *dev, int which) +{ + /* + * Get some memory. + */ + memset (dev, 0, sizeof (struct sbull_dev)); + dev->size = nsectors*hardsect_size; + dev->data = vmalloc(dev->size); + if (dev->data == NULL) { + printk (KERN_NOTICE "vmalloc failure.\n"); + return; + } + spin_lock_init(&dev->lock); + + /* + * The timer which "invalidates" the device. + */ + init_timer(&dev->timer); + dev->timer.data = (unsigned long) dev; + dev->timer.function = sbull_invalidate; + + /* + * The I/O queue, depending on whether we are using our own + * make_request function or not. + */ + switch (request_mode) { + case RM_NOQUEUE: + dev->queue = blk_alloc_queue(GFP_KERNEL); + if (dev->queue == NULL) + goto out_vfree; + blk_queue_make_request(dev->queue, sbull_make_request); + break; + + case RM_FULL: + dev->queue = blk_init_queue(sbull_full_request, &dev->lock); + if (dev->queue == NULL) + goto out_vfree; + break; + + default: + printk(KERN_NOTICE "Bad request mode %d, using simple\n", request_mode); + /* fall into.. */ + + case RM_SIMPLE: + dev->queue = blk_init_queue(sbull_request, &dev->lock); + if (dev->queue == NULL) + goto out_vfree; + break; + } + blk_queue_hardsect_size(dev->queue, hardsect_size); + dev->queue->queuedata = dev; + /* + * And the gendisk structure. + */ + dev->gd = alloc_disk(SBULL_MINORS); + if (! dev->gd) { + printk (KERN_NOTICE "alloc_disk failure\n"); + goto out_vfree; + } + dev->gd->major = sbull_major; + dev->gd->first_minor = which*SBULL_MINORS; + dev->gd->fops = &sbull_ops; + dev->gd->queue = dev->queue; + dev->gd->private_data = dev; + snprintf (dev->gd->disk_name, 32, "sbull%c", which + 'a'); + set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE)); + add_disk(dev->gd); + return; + + out_vfree: + if (dev->data) + vfree(dev->data); +} + + + +static int __init sbull_init(void) +{ + int i; + /* + * Get registered. + */ + sbull_major = register_blkdev(sbull_major, "sbull"); + if (sbull_major <= 0) { + printk(KERN_WARNING "sbull: unable to get major number\n"); + return -EBUSY; + } + /* + * Allocate the device array, and initialize each one. + */ + Devices = kmalloc(ndevices*sizeof (struct sbull_dev), GFP_KERNEL); + if (Devices == NULL) + goto out_unregister; + for (i = 0; i < ndevices; i++) + setup_device(Devices + i, i); + + return 0; + + out_unregister: + unregister_blkdev(sbull_major, "sbd"); + return -ENOMEM; +} + +static void sbull_exit(void) +{ + int i; + + for (i = 0; i < ndevices; i++) { + struct sbull_dev *dev = Devices + i; + + del_timer_sync(&dev->timer); + if (dev->gd) { + del_gendisk(dev->gd); + put_disk(dev->gd); + } + if (dev->queue) { + if (request_mode == RM_NOQUEUE) + blk_put_queue(dev->queue); + else + blk_cleanup_queue(dev->queue); + } + if (dev->data) + vfree(dev->data); + } + unregister_blkdev(sbull_major, "sbull"); + kfree(Devices); +} + +module_init(sbull_init); +module_exit(sbull_exit); diff --git a/sbull/sbull.h b/sbull/sbull.h new file mode 100644 index 0000000..c443d70 --- /dev/null +++ b/sbull/sbull.h @@ -0,0 +1,71 @@ + +/* + * sbull.h -- definitions for the char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + */ + + +#include <linux/ioctl.h> + +/* Multiqueue only works on 2.4 */ +#ifdef SBULL_MULTIQUEUE +# warning "Multiqueue only works on 2.4 kernels" +#endif + +/* + * Macros to help debugging + */ + +#undef PDEBUG /* undef it, just in case */ +#ifdef SBULL_DEBUG +# ifdef __KERNEL__ + /* This one if debugging is on, and kernel space */ +# define PDEBUG(fmt, args...) printk( KERN_DEBUG "sbull: " fmt, ## args) +# else + /* This one for user space */ +# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) +# endif +#else +# define PDEBUG(fmt, args...) /* not debugging: nothing */ +#endif + +#undef PDEBUGG +#define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */ + + +#define SBULL_MAJOR 0 /* dynamic major by default */ +#define SBULL_DEVS 2 /* two disks */ +#define SBULL_RAHEAD 2 /* two sectors */ +#define SBULL_SIZE 2048 /* two megs each */ +#define SBULL_BLKSIZE 1024 /* 1k blocks */ +#define SBULL_HARDSECT 512 /* 2.2 and 2.4 can used different values */ + +#define SBULLR_MAJOR 0 /* Dynamic major for raw device */ +/* + * The sbull device is removable: if it is left closed for more than + * half a minute, it is removed. Thus use a usage count and a + * kernel timer + */ + +typedef struct Sbull_Dev { + int size; + int usage; + struct timer_list timer; + spinlock_t lock; + u8 *data; +#ifdef SBULL_MULTIQUEUE + request_queue_t *queue; + int busy; +#endif +} Sbull_Dev; diff --git a/sbull/sbull_load b/sbull/sbull_load new file mode 100644 index 0000000..8cdec56 --- /dev/null +++ b/sbull/sbull_load @@ -0,0 +1,47 @@ +#!/bin/sh + +function make_minors { + let part=1 + while (($part < $minors)); do + let minor=$part+$2 + mknod $1$part b $major $minor + let part=$part+1 + done +} + + +# FIXME: This isn't handling minors (partitions) at all. +module="sbull" +device="sbull" +mode="664" +chardevice="sbullr" +minors=16 + +# Group: since distributions do it differently, look for wheel or use staff +if grep '^staff:' /etc/group > /dev/null; then + group="staff" +else + group="wheel" +fi + +# invoke insmod with all arguments we got +# and use a pathname, as newer modutils don't look in . by default +/sbin/insmod -f ./$module.ko $* || exit 1 + +major=`cat /proc/devices | awk "\\$2==\"$module\" {print \\$1}"` + +# Remove stale nodes and replace them, then give gid and perms + +rm -f /dev/${device}[a-d]* /dev/${device} + +mknod /dev/${device}a b $major 0 +make_minors /dev/${device}a 0 +mknod /dev/${device}b b $major 16 +make_minors /dev/${device}b 16 +mknod /dev/${device}c b $major 32 +make_minors /dev/${device}c 32 +mknod /dev/${device}d b $major 48 +make_minors /dev/${device}d 48 +ln -sf ${device}a /dev/${device} +chgrp $group /dev/${device}[a-d]* +chmod $mode /dev/${device}[a-d]* diff --git a/sbull/sbull_unload b/sbull/sbull_unload new file mode 100644 index 0000000..aa22dfb --- /dev/null +++ b/sbull/sbull_unload @@ -0,0 +1,14 @@ +#!/bin/sh +module="sbull" +device="sbull" + +# invoke rmmod with all arguments we got +/sbin/rmmod $module $* || exit 1 + +# Remove stale nodes +rm -f /dev/${device}[a-d]* /dev/${device} + + + + + diff --git a/scull/Makefile b/scull/Makefile new file mode 100644 index 0000000..089b144 --- /dev/null +++ b/scull/Makefile @@ -0,0 +1,43 @@ +# Comment/uncomment the following line to disable/enable debugging +#DEBUG = y + + +# Add your debugging flag (or not) to CFLAGS +ifeq ($(DEBUG),y) + DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines +else + DEBFLAGS = -O2 +endif + +CFLAGS += $(DEBFLAGS) +CFLAGS += -I$(LDDINC) + +ifneq ($(KERNELRELEASE),) +# call from kernel build system + +scull-objs := main.o pipe.o access.o + +obj-m := scull.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +modules: + $(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD)/../include modules + +endif + + + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/scull/SCULL~1.INI b/scull/SCULL~1.INI new file mode 100755 index 0000000..e0523ce --- /dev/null +++ b/scull/SCULL~1.INI @@ -0,0 +1,142 @@ +#!/bin/bash +# Sample init script for the a driver module <rubini@linux.it> + +DEVICE="scull" +SECTION="misc" + +# The list of filenames and minor numbers: $PREFIX is prefixed to all names +PREFIX="scull" +FILES=" 0 0 1 1 2 2 3 3 priv 16 + pipe0 32 pipe1 33 pipe2 34 pipe3 35 + single 48 uid 64 wuid 80" + +INSMOD=/sbin/insmod; # use /sbin/modprobe if you prefer + +function device_specific_post_load () { + true; # fill at will +} +function device_specific_pre_unload () { + true; # fill at will +} + +# Everything below this line should work unchanged for any char device. +# Obviously, however, no options on the command line: either in +# /etc/${DEVICE}.conf or /etc/modules.conf (if modprobe is used) + +# Optional configuration file: format is +# owner <ownername> +# group <groupname> +# mode <modename> +# options <insmod options> +CFG=/etc/${DEVICE}.conf + +# kernel version, used to look for modules +KERNEL=`uname -r` + +#FIXME: it looks like there is no misc section. Where should it be? +MODDIR="/lib/modules/${KERNEL}/kernel/drivers/${SECTION}" +if [ ! -d $MODDIR ]; then MODDIR="/lib/modules/${KERNEL}/${SECTION}"; fi + +# Root or die +if [ "$(id -u)" != "0" ] +then + echo "You must be root to load or unload kernel modules" + exit 1 +fi + +# Read configuration file +if [ -r $CFG ]; then + OWNER=`awk "\\$1==\"owner\" {print \\$2}" $CFG` + GROUP=`awk "\\$1==\"group\" {print \\$2}" $CFG` + MODE=`awk "\\$1==\"mode\" {print \\$2}" $CFG` + # The options string may include extra blanks or only blanks + OPTIONS=`sed -n '/^options / s/options //p' $CFG` +fi + + +# Create device files +function create_files () { + cd /dev + local devlist="" + local file + while true; do + if [ $# -lt 2 ]; then break; fi + file="${DEVICE}$1" + mknod $file c $MAJOR $2 + devlist="$devlist $file" + shift 2 + done + if [ -n "$OWNER" ]; then chown $OWNER $devlist; fi + if [ -n "$GROUP" ]; then chgrp $GROUP $devlist; fi + if [ -n "$MODE" ]; then chmod $MODE $devlist; fi +} + +# Remove device files +function remove_files () { + cd /dev + local devlist="" + local file + while true; do + if [ $# -lt 2 ]; then break; fi + file="${DEVICE}$1" + devlist="$devlist $file" + shift 2 + done + rm -f $devlist +} + +# Load and create files +function load_device () { + + if [ -f $MODDIR/$DEVICE.o ]; then + devpath=$MODDIR/$DEVICE.o + else if [ -f ./$DEVICE.o ]; then + devpath=./$DEVICE.o + else + devpath=$DEVICE; # let insmod/modprobe guess + fi; fi + if [ "$devpath" != "$DEVICE" ]; then + echo -n " (loading file $devpath)" + fi + + if $INSMOD $devpath $OPTIONS; then + MAJOR=`awk "\\$2==\"$DEVICE\" {print \\$1}" /proc/devices` + remove_files $FILES + create_files $FILES + device_specific_post_load + else + echo " FAILED!" + fi +} + +# Unload and remove files +function unload_device () { + device_specific_pre_unload + /sbin/rmmod $DEVICE + remove_files $FILES +} + + +case "$1" in + start) + echo -n "Loading $DEVICE" + load_device + echo "." + ;; + stop) + echo -n "Unloading $DEVICE" + unload_device + echo "." + ;; + force-reload|restart) + echo -n "Reloading $DEVICE" + unload_device + load_device + echo "." + ;; + *) + echo "Usage: $0 {start|stop|restart|force-reload}" + exit 1 +esac + +exit 0 diff --git a/scull/access.c b/scull/access.c new file mode 100644 index 0000000..970b57c --- /dev/null +++ b/scull/access.c @@ -0,0 +1,410 @@ +/* + * access.c -- the files with access control on open + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: access.c,v 1.17 2004/09/26 07:29:56 gregkh Exp $ + */ + +/* FIXME: cloned devices as a use for kobjects? */ + +#include <linux/kernel.h> /* printk() */ +#include <linux/module.h> +#include <linux/slab.h> /* kmalloc() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/types.h> /* size_t */ +#include <linux/fcntl.h> +#include <linux/cdev.h> +#include <linux/tty.h> +#include <asm/atomic.h> +#include <linux/list.h> + +#include "scull.h" /* local definitions */ + +static dev_t scull_a_firstdev; /* Where our range begins */ + +/* + * These devices fall back on the main scull operations. They only + * differ in the implementation of open() and close() + */ + + + +/************************************************************************ + * + * The first device is the single-open one, + * it has an hw structure and an open count + */ + +static struct scull_dev scull_s_device; +static atomic_t scull_s_available = ATOMIC_INIT(1); + +static int scull_s_open(struct inode *inode, struct file *filp) +{ + struct scull_dev *dev = &scull_s_device; /* device information */ + + if (! atomic_dec_and_test (&scull_s_available)) { + atomic_inc(&scull_s_available); + return -EBUSY; /* already open */ + } + + /* then, everything else is copied from the bare scull device */ + if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) + scull_trim(dev); + filp->private_data = dev; + return 0; /* success */ +} + +static int scull_s_release(struct inode *inode, struct file *filp) +{ + atomic_inc(&scull_s_available); /* release the device */ + return 0; +} + + +/* + * The other operations for the single-open device come from the bare device + */ +struct file_operations scull_sngl_fops = { + .owner = THIS_MODULE, + .llseek = scull_llseek, + .read = scull_read, + .write = scull_write, + .ioctl = scull_ioctl, + .open = scull_s_open, + .release = scull_s_release, +}; + + +/************************************************************************ + * + * Next, the "uid" device. It can be opened multiple times by the + * same user, but access is denied to other users if the device is open + */ + +static struct scull_dev scull_u_device; +static int scull_u_count; /* initialized to 0 by default */ +static uid_t scull_u_owner; /* initialized to 0 by default */ +static spinlock_t scull_u_lock = SPIN_LOCK_UNLOCKED; + +static int scull_u_open(struct inode *inode, struct file *filp) +{ + struct scull_dev *dev = &scull_u_device; /* device information */ + + spin_lock(&scull_u_lock); + if (scull_u_count && + (scull_u_owner != current->uid) && /* allow user */ + (scull_u_owner != current->euid) && /* allow whoever did su */ + !capable(CAP_DAC_OVERRIDE)) { /* still allow root */ + spin_unlock(&scull_u_lock); + return -EBUSY; /* -EPERM would confuse the user */ + } + + if (scull_u_count == 0) + scull_u_owner = current->uid; /* grab it */ + + scull_u_count++; + spin_unlock(&scull_u_lock); + +/* then, everything else is copied from the bare scull device */ + + if ((filp->f_flags & O_ACCMODE) == O_WRONLY) + scull_trim(dev); + filp->private_data = dev; + return 0; /* success */ +} + +static int scull_u_release(struct inode *inode, struct file *filp) +{ + spin_lock(&scull_u_lock); + scull_u_count--; /* nothing else */ + spin_unlock(&scull_u_lock); + return 0; +} + + + +/* + * The other operations for the device come from the bare device + */ +struct file_operations scull_user_fops = { + .owner = THIS_MODULE, + .llseek = scull_llseek, + .read = scull_read, + .write = scull_write, + .ioctl = scull_ioctl, + .open = scull_u_open, + .release = scull_u_release, +}; + + +/************************************************************************ + * + * Next, the device with blocking-open based on uid + */ + +static struct scull_dev scull_w_device; +static int scull_w_count; /* initialized to 0 by default */ +static uid_t scull_w_owner; /* initialized to 0 by default */ +static DECLARE_WAIT_QUEUE_HEAD(scull_w_wait); +static spinlock_t scull_w_lock = SPIN_LOCK_UNLOCKED; + +static inline int scull_w_available(void) +{ + return scull_w_count == 0 || + scull_w_owner == current->uid || + scull_w_owner == current->euid || + capable(CAP_DAC_OVERRIDE); +} + + +static int scull_w_open(struct inode *inode, struct file *filp) +{ + struct scull_dev *dev = &scull_w_device; /* device information */ + + spin_lock(&scull_w_lock); + while (! scull_w_available()) { + spin_unlock(&scull_w_lock); + if (filp->f_flags & O_NONBLOCK) return -EAGAIN; + if (wait_event_interruptible (scull_w_wait, scull_w_available())) + return -ERESTARTSYS; /* tell the fs layer to handle it */ + spin_lock(&scull_w_lock); + } + if (scull_w_count == 0) + scull_w_owner = current->uid; /* grab it */ + scull_w_count++; + spin_unlock(&scull_w_lock); + + /* then, everything else is copied from the bare scull device */ + if ((filp->f_flags & O_ACCMODE) == O_WRONLY) + scull_trim(dev); + filp->private_data = dev; + return 0; /* success */ +} + +static int scull_w_release(struct inode *inode, struct file *filp) +{ + int temp; + + spin_lock(&scull_w_lock); + scull_w_count--; + temp = scull_w_count; + spin_unlock(&scull_w_lock); + + if (temp == 0) + wake_up_interruptible_sync(&scull_w_wait); /* awake other uid's */ + return 0; +} + + +/* + * The other operations for the device come from the bare device + */ +struct file_operations scull_wusr_fops = { + .owner = THIS_MODULE, + .llseek = scull_llseek, + .read = scull_read, + .write = scull_write, + .ioctl = scull_ioctl, + .open = scull_w_open, + .release = scull_w_release, +}; + +/************************************************************************ + * + * Finally the `cloned' private device. This is trickier because it + * involves list management, and dynamic allocation. + */ + +/* The clone-specific data structure includes a key field */ + +struct scull_listitem { + struct scull_dev device; + dev_t key; + struct list_head list; + +}; + +/* The list of devices, and a lock to protect it */ +static LIST_HEAD(scull_c_list); +static spinlock_t scull_c_lock = SPIN_LOCK_UNLOCKED; + +/* A placeholder scull_dev which really just holds the cdev stuff. */ +static struct scull_dev scull_c_device; + +/* Look for a device or create one if missing */ +static struct scull_dev *scull_c_lookfor_device(dev_t key) +{ + struct scull_listitem *lptr; + + list_for_each_entry(lptr, &scull_c_list, list) { + if (lptr->key == key) + return &(lptr->device); + } + + /* not found */ + lptr = kmalloc(sizeof(struct scull_listitem), GFP_KERNEL); + if (!lptr) + return NULL; + + /* initialize the device */ + memset(lptr, 0, sizeof(struct scull_listitem)); + lptr->key = key; + scull_trim(&(lptr->device)); /* initialize it */ + init_MUTEX(&(lptr->device.sem)); + + /* place it in the list */ + list_add(&lptr->list, &scull_c_list); + + return &(lptr->device); +} + +static int scull_c_open(struct inode *inode, struct file *filp) +{ + struct scull_dev *dev; + dev_t key; + + if (!current->signal->tty) { + PDEBUG("Process \"%s\" has no ctl tty\n", current->comm); + return -EINVAL; + } + key = tty_devnum(current->signal->tty); + + /* look for a scullc device in the list */ + spin_lock(&scull_c_lock); + dev = scull_c_lookfor_device(key); + spin_unlock(&scull_c_lock); + + if (!dev) + return -ENOMEM; + + /* then, everything else is copied from the bare scull device */ + if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) + scull_trim(dev); + filp->private_data = dev; + return 0; /* success */ +} + +static int scull_c_release(struct inode *inode, struct file *filp) +{ + /* + * Nothing to do, because the device is persistent. + * A `real' cloned device should be freed on last close + */ + return 0; +} + + + +/* + * The other operations for the device come from the bare device + */ +struct file_operations scull_priv_fops = { + .owner = THIS_MODULE, + .llseek = scull_llseek, + .read = scull_read, + .write = scull_write, + .ioctl = scull_ioctl, + .open = scull_c_open, + .release = scull_c_release, +}; + +/************************************************************************ + * + * And the init and cleanup functions come last + */ + +static struct scull_adev_info { + char *name; + struct scull_dev *sculldev; + struct file_operations *fops; +} scull_access_devs[] = { + { "scullsingle", &scull_s_device, &scull_sngl_fops }, + { "sculluid", &scull_u_device, &scull_user_fops }, + { "scullwuid", &scull_w_device, &scull_wusr_fops }, + { "sullpriv", &scull_c_device, &scull_priv_fops } +}; +#define SCULL_N_ADEVS 4 + +/* + * Set up a single device. + */ +static void scull_access_setup (dev_t devno, struct scull_adev_info *devinfo) +{ + struct scull_dev *dev = devinfo->sculldev; + int err; + + /* Initialize the device structure */ + dev->quantum = scull_quantum; + dev->qset = scull_qset; + init_MUTEX(&dev->sem); + + /* Do the cdev stuff. */ + cdev_init(&dev->cdev, devinfo->fops); + kobject_set_name(&dev->cdev.kobj, devinfo->name); + dev->cdev.owner = THIS_MODULE; + err = cdev_add (&dev->cdev, devno, 1); + /* Fail gracefully if need be */ + if (err) { + printk(KERN_NOTICE "Error %d adding %s\n", err, devinfo->name); + kobject_put(&dev->cdev.kobj); + } else + printk(KERN_NOTICE "%s registered at %x\n", devinfo->name, devno); +} + + +int scull_access_init(dev_t firstdev) +{ + int result, i; + + /* Get our number space */ + result = register_chrdev_region (firstdev, SCULL_N_ADEVS, "sculla"); + if (result < 0) { + printk(KERN_WARNING "sculla: device number registration failed\n"); + return 0; + } + scull_a_firstdev = firstdev; + + /* Set up each device. */ + for (i = 0; i < SCULL_N_ADEVS; i++) + scull_access_setup (firstdev + i, scull_access_devs + i); + return SCULL_N_ADEVS; +} + +/* + * This is called by cleanup_module or on failure. + * It is required to never fail, even if nothing was initialized first + */ +void scull_access_cleanup(void) +{ + struct scull_listitem *lptr, *next; + int i; + + /* Clean up the static devs */ + for (i = 0; i < SCULL_N_ADEVS; i++) { + struct scull_dev *dev = scull_access_devs[i].sculldev; + cdev_del(&dev->cdev); + scull_trim(scull_access_devs[i].sculldev); + } + + /* And all the cloned devices */ + list_for_each_entry_safe(lptr, next, &scull_c_list, list) { + list_del(&lptr->list); + scull_trim(&(lptr->device)); + kfree(lptr); + } + + /* Free up our number space */ + unregister_chrdev_region(scull_a_firstdev, SCULL_N_ADEVS); + return; +} diff --git a/scull/main.c b/scull/main.c new file mode 100644 index 0000000..b1d9e3a --- /dev/null +++ b/scull/main.c @@ -0,0 +1,673 @@ +/* + * main.c -- the bare scull char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include <linux/kernel.h> /* printk() */ +#include <linux/slab.h> /* kmalloc() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/types.h> /* size_t */ +#include <linux/proc_fs.h> +#include <linux/fcntl.h> /* O_ACCMODE */ +#include <linux/seq_file.h> +#include <linux/cdev.h> + +#include <asm/system.h> /* cli(), *_flags */ +#include <asm/uaccess.h> /* copy_*_user */ + +#include "scull.h" /* local definitions */ + +/* + * Our parameters which can be set at load time. + */ + +int scull_major = SCULL_MAJOR; +int scull_minor = 0; +int scull_nr_devs = SCULL_NR_DEVS; /* number of bare scull devices */ +int scull_quantum = SCULL_QUANTUM; +int scull_qset = SCULL_QSET; + +module_param(scull_major, int, S_IRUGO); +module_param(scull_minor, int, S_IRUGO); +module_param(scull_nr_devs, int, S_IRUGO); +module_param(scull_quantum, int, S_IRUGO); +module_param(scull_qset, int, S_IRUGO); + +MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet"); +MODULE_LICENSE("Dual BSD/GPL"); + +struct scull_dev *scull_devices; /* allocated in scull_init_module */ + + +/* + * Empty out the scull device; must be called with the device + * semaphore held. + */ +int scull_trim(struct scull_dev *dev) +{ + struct scull_qset *next, *dptr; + int qset = dev->qset; /* "dev" is not-null */ + int i; + + for (dptr = dev->data; dptr; dptr = next) { /* all the list items */ + if (dptr->data) { + for (i = 0; i < qset; i++) + kfree(dptr->data[i]); + kfree(dptr->data); + dptr->data = NULL; + } + next = dptr->next; + kfree(dptr); + } + dev->size = 0; + dev->quantum = scull_quantum; + dev->qset = scull_qset; + dev->data = NULL; + return 0; +} +#ifdef SCULL_DEBUG /* use proc only if debugging */ +/* + * The proc filesystem: function to read and entry + */ + +int scull_read_procmem(char *buf, char **start, off_t offset, + int count, int *eof, void *data) +{ + int i, j, len = 0; + int limit = count - 80; /* Don't print more than this */ + + for (i = 0; i < scull_nr_devs && len <= limit; i++) { + struct scull_dev *d = &scull_devices[i]; + struct scull_qset *qs = d->data; + if (down_interruptible(&d->sem)) + return -ERESTARTSYS; + len += sprintf(buf+len,"\nDevice %i: qset %i, q %i, sz %li\n", + i, d->qset, d->quantum, d->size); + for (; qs && len <= limit; qs = qs->next) { /* scan the list */ + len += sprintf(buf + len, " item at %p, qset at %p\n", + qs, qs->data); + if (qs->data && !qs->next) /* dump only the last item */ + for (j = 0; j < d->qset; j++) { + if (qs->data[j]) + len += sprintf(buf + len, + " % 4i: %8p\n", + j, qs->data[j]); + } + } + up(&scull_devices[i].sem); + } + *eof = 1; + return len; +} + + +/* + * For now, the seq_file implementation will exist in parallel. The + * older read_procmem function should maybe go away, though. + */ + +/* + * Here are our sequence iteration methods. Our "position" is + * simply the device number. + */ +static void *scull_seq_start(struct seq_file *s, loff_t *pos) +{ + if (*pos >= scull_nr_devs) + return NULL; /* No more to read */ + return scull_devices + *pos; +} + +static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + (*pos)++; + if (*pos >= scull_nr_devs) + return NULL; + return scull_devices + *pos; +} + +static void scull_seq_stop(struct seq_file *s, void *v) +{ + /* Actually, there's nothing to do here */ +} + +static int scull_seq_show(struct seq_file *s, void *v) +{ + struct scull_dev *dev = (struct scull_dev *) v; + struct scull_qset *d; + int i; + + if (down_interruptible(&dev->sem)) + return -ERESTARTSYS; + seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n", + (int) (dev - scull_devices), dev->qset, + dev->quantum, dev->size); + for (d = dev->data; d; d = d->next) { /* scan the list */ + seq_printf(s, " item at %p, qset at %p\n", d, d->data); + if (d->data && !d->next) /* dump only the last item */ + for (i = 0; i < dev->qset; i++) { + if (d->data[i]) + seq_printf(s, " % 4i: %8p\n", + i, d->data[i]); + } + } + up(&dev->sem); + return 0; +} + +/* + * Tie the sequence operators up. + */ +static struct seq_operations scull_seq_ops = { + .start = scull_seq_start, + .next = scull_seq_next, + .stop = scull_seq_stop, + .show = scull_seq_show +}; + +/* + * Now to implement the /proc file we need only make an open + * method which sets up the sequence operators. + */ +static int scull_proc_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &scull_seq_ops); +} + +/* + * Create a set of file operations for our proc file. + */ +static struct file_operations scull_proc_ops = { + .owner = THIS_MODULE, + .open = scull_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release +}; + + +/* + * Actually create (and remove) the /proc file(s). + */ + +static void scull_create_proc(void) +{ + struct proc_dir_entry *entry; + create_proc_read_entry("scullmem", 0 /* default mode */, + NULL /* parent dir */, scull_read_procmem, + NULL /* client data */); + entry = create_proc_entry("scullseq", 0, NULL); + if (entry) + entry->proc_fops = &scull_proc_ops; +} + +static void scull_remove_proc(void) +{ + /* no problem if it was not registered */ + remove_proc_entry("scullmem", NULL /* parent dir */); + remove_proc_entry("scullseq", NULL); +} + + +#endif /* SCULL_DEBUG */ + + + + + +/* + * Open and close + */ + +int scull_open(struct inode *inode, struct file *filp) +{ + struct scull_dev *dev; /* device information */ + + dev = container_of(inode->i_cdev, struct scull_dev, cdev); + filp->private_data = dev; /* for other methods */ + + /* now trim to 0 the length of the device if open was write-only */ + if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) { + if (down_interruptible(&dev->sem)) + return -ERESTARTSYS; + scull_trim(dev); /* ignore errors */ + up(&dev->sem); + } + return 0; /* success */ +} + +int scull_release(struct inode *inode, struct file *filp) +{ + return 0; +} +/* + * Follow the list + */ +struct scull_qset *scull_follow(struct scull_dev *dev, int n) +{ + struct scull_qset *qs = dev->data; + + /* Allocate first qset explicitly if need be */ + if (! qs) { + qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL); + if (qs == NULL) + return NULL; /* Never mind */ + memset(qs, 0, sizeof(struct scull_qset)); + } + + /* Then follow the list */ + while (n--) { + if (!qs->next) { + qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL); + if (qs->next == NULL) + return NULL; /* Never mind */ + memset(qs->next, 0, sizeof(struct scull_qset)); + } + qs = qs->next; + continue; + } + return qs; +} + +/* + * Data management: read and write + */ + +ssize_t scull_read(struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + struct scull_dev *dev = filp->private_data; + struct scull_qset *dptr; /* the first listitem */ + int quantum = dev->quantum, qset = dev->qset; + int itemsize = quantum * qset; /* how many bytes in the listitem */ + int item, s_pos, q_pos, rest; + ssize_t retval = 0; + + if (down_interruptible(&dev->sem)) + return -ERESTARTSYS; + if (*f_pos >= dev->size) + goto out; + if (*f_pos + count > dev->size) + count = dev->size - *f_pos; + + /* find listitem, qset index, and offset in the quantum */ + item = (long)*f_pos / itemsize; + rest = (long)*f_pos % itemsize; + s_pos = rest / quantum; q_pos = rest % quantum; + + /* follow the list up to the right position (defined elsewhere) */ + dptr = scull_follow(dev, item); + + if (dptr == NULL || !dptr->data || ! dptr->data[s_pos]) + goto out; /* don't fill holes */ + + /* read only up to the end of this quantum */ + if (count > quantum - q_pos) + count = quantum - q_pos; + + if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) { + retval = -EFAULT; + goto out; + } + *f_pos += count; + retval = count; + + out: + up(&dev->sem); + return retval; +} + +ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, + loff_t *f_pos) +{ + struct scull_dev *dev = filp->private_data; + struct scull_qset *dptr; + int quantum = dev->quantum, qset = dev->qset; + int itemsize = quantum * qset; + int item, s_pos, q_pos, rest; + ssize_t retval = -ENOMEM; /* value used in "goto out" statements */ + + if (down_interruptible(&dev->sem)) + return -ERESTARTSYS; + + /* find listitem, qset index and offset in the quantum */ + item = (long)*f_pos / itemsize; + rest = (long)*f_pos % itemsize; + s_pos = rest / quantum; q_pos = rest % quantum; + + /* follow the list up to the right position */ + dptr = scull_follow(dev, item); + if (dptr == NULL) + goto out; + if (!dptr->data) { + dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL); + if (!dptr->data) + goto out; + memset(dptr->data, 0, qset * sizeof(char *)); + } + if (!dptr->data[s_pos]) { + dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL); + if (!dptr->data[s_pos]) + goto out; + } + /* write only up to the end of this quantum */ + if (count > quantum - q_pos) + count = quantum - q_pos; + + if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) { + retval = -EFAULT; + goto out; + } + *f_pos += count; + retval = count; + + /* update the size */ + if (dev->size < *f_pos) + dev->size = *f_pos; + + out: + up(&dev->sem); + return retval; +} + +/* + * The ioctl() implementation + */ + +int scull_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + + int err = 0, tmp; + int retval = 0; + + /* + * extract the type and number bitfields, and don't decode + * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() + */ + if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY; + if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY; + + /* + * the direction is a bitmask, and VERIFY_WRITE catches R/W + * transfers. `Type' is user-oriented, while + * access_ok is kernel-oriented, so the concept of "read" and + * "write" is reversed + */ + if (_IOC_DIR(cmd) & _IOC_READ) + err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); + else if (_IOC_DIR(cmd) & _IOC_WRITE) + err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); + if (err) return -EFAULT; + + switch(cmd) { + + case SCULL_IOCRESET: + scull_quantum = SCULL_QUANTUM; + scull_qset = SCULL_QSET; + break; + + case SCULL_IOCSQUANTUM: /* Set: arg points to the value */ + if (! capable (CAP_SYS_ADMIN)) + return -EPERM; + retval = __get_user(scull_quantum, (int __user *)arg); + break; + + case SCULL_IOCTQUANTUM: /* Tell: arg is the value */ + if (! capable (CAP_SYS_ADMIN)) + return -EPERM; + scull_quantum = arg; + break; + + case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */ + retval = __put_user(scull_quantum, (int __user *)arg); + break; + + case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */ + return scull_quantum; + + case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */ + if (! capable (CAP_SYS_ADMIN)) + return -EPERM; + tmp = scull_quantum; + retval = __get_user(scull_quantum, (int __user *)arg); + if (retval == 0) + retval = __put_user(tmp, (int __user *)arg); + break; + + case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */ + if (! capable (CAP_SYS_ADMIN)) + return -EPERM; + tmp = scull_quantum; + scull_quantum = arg; + return tmp; + + case SCULL_IOCSQSET: + if (! capable (CAP_SYS_ADMIN)) + return -EPERM; + retval = __get_user(scull_qset, (int __user *)arg); + break; + + case SCULL_IOCTQSET: + if (! capable (CAP_SYS_ADMIN)) + return -EPERM; + scull_qset = arg; + break; + + case SCULL_IOCGQSET: + retval = __put_user(scull_qset, (int __user *)arg); + break; + + case SCULL_IOCQQSET: + return scull_qset; + + case SCULL_IOCXQSET: + if (! capable (CAP_SYS_ADMIN)) + return -EPERM; + tmp = scull_qset; + retval = __get_user(scull_qset, (int __user *)arg); + if (retval == 0) + retval = put_user(tmp, (int __user *)arg); + break; + + case SCULL_IOCHQSET: + if (! capable (CAP_SYS_ADMIN)) + return -EPERM; + tmp = scull_qset; + scull_qset = arg; + return tmp; + + /* + * The following two change the buffer size for scullpipe. + * The scullpipe device uses this same ioctl method, just to + * write less code. Actually, it's the same driver, isn't it? + */ + + case SCULL_P_IOCTSIZE: + scull_p_buffer = arg; + break; + + case SCULL_P_IOCQSIZE: + return scull_p_buffer; + + + default: /* redundant, as cmd was checked against MAXNR */ + return -ENOTTY; + } + return retval; + +} + + + +/* + * The "extended" operations -- only seek + */ + +loff_t scull_llseek(struct file *filp, loff_t off, int whence) +{ + struct scull_dev *dev = filp->private_data; + loff_t newpos; + + switch(whence) { + case 0: /* SEEK_SET */ + newpos = off; + break; + + case 1: /* SEEK_CUR */ + newpos = filp->f_pos + off; + break; + + case 2: /* SEEK_END */ + newpos = dev->size + off; + break; + + default: /* can't happen */ + return -EINVAL; + } + if (newpos < 0) return -EINVAL; + filp->f_pos = newpos; + return newpos; +} + + + +struct file_operations scull_fops = { + .owner = THIS_MODULE, + .llseek = scull_llseek, + .read = scull_read, + .write = scull_write, + .ioctl = scull_ioctl, + .open = scull_open, + .release = scull_release, +}; + +/* + * Finally, the module stuff + */ + +/* + * The cleanup function is used to handle initialization failures as well. + * Thefore, it must be careful to work correctly even if some of the items + * have not been initialized + */ +void scull_cleanup_module(void) +{ + int i; + dev_t devno = MKDEV(scull_major, scull_minor); + + /* Get rid of our char dev entries */ + if (scull_devices) { + for (i = 0; i < scull_nr_devs; i++) { + scull_trim(scull_devices + i); + cdev_del(&scull_devices[i].cdev); + } + kfree(scull_devices); + } + +#ifdef SCULL_DEBUG /* use proc only if debugging */ + scull_remove_proc(); +#endif + + /* cleanup_module is never called if registering failed */ + unregister_chrdev_region(devno, scull_nr_devs); + + /* and call the cleanup functions for friend devices */ + scull_p_cleanup(); + scull_access_cleanup(); + +} + + +/* + * Set up the char_dev structure for this device. + */ +static void scull_setup_cdev(struct scull_dev *dev, int index) +{ + int err, devno = MKDEV(scull_major, scull_minor + index); + + cdev_init(&dev->cdev, &scull_fops); + dev->cdev.owner = THIS_MODULE; + dev->cdev.ops = &scull_fops; + err = cdev_add (&dev->cdev, devno, 1); + /* Fail gracefully if need be */ + if (err) + printk(KERN_NOTICE "Error %d adding scull%d", err, index); +} + + +int scull_init_module(void) +{ + int result, i; + dev_t dev = 0; + +/* + * Get a range of minor numbers to work with, asking for a dynamic + * major unless directed otherwise at load time. + */ + if (scull_major) { + dev = MKDEV(scull_major, scull_minor); + result = register_chrdev_region(dev, scull_nr_devs, "scull"); + } else { + result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, + "scull"); + scull_major = MAJOR(dev); + } + if (result < 0) { + printk(KERN_WARNING "scull: can't get major %d\n", scull_major); + return result; + } + + /* + * allocate the devices -- we can't have them static, as the number + * can be specified at load time + */ + scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL); + if (!scull_devices) { + result = -ENOMEM; + goto fail; /* Make this more graceful */ + } + memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev)); + + /* Initialize each device. */ + for (i = 0; i < scull_nr_devs; i++) { + scull_devices[i].quantum = scull_quantum; + scull_devices[i].qset = scull_qset; + init_MUTEX(&scull_devices[i].sem); + scull_setup_cdev(&scull_devices[i], i); + } + + /* At this point call the init function for any friend device */ + dev = MKDEV(scull_major, scull_minor + scull_nr_devs); + dev += scull_p_init(dev); + dev += scull_access_init(dev); + +#ifdef SCULL_DEBUG /* only when debugging */ + scull_create_proc(); +#endif + + return 0; /* succeed */ + + fail: + scull_cleanup_module(); + return result; +} + +module_init(scull_init_module); +module_exit(scull_cleanup_module); diff --git a/scull/pipe.c b/scull/pipe.c new file mode 100644 index 0000000..9a60b83 --- /dev/null +++ b/scull/pipe.c @@ -0,0 +1,396 @@ +/* + * pipe.c -- fifo driver for scull + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> + +#include <linux/kernel.h> /* printk(), min() */ +#include <linux/slab.h> /* kmalloc() */ +#include <linux/fs.h> /* everything... */ +#include <linux/proc_fs.h> +#include <linux/errno.h> /* error codes */ +#include <linux/types.h> /* size_t */ +#include <linux/fcntl.h> +#include <linux/poll.h> +#include <linux/cdev.h> +#include <asm/uaccess.h> + +#include "scull.h" /* local definitions */ + +struct scull_pipe { + wait_queue_head_t inq, outq; /* read and write queues */ + char *buffer, *end; /* begin of buf, end of buf */ + int buffersize; /* used in pointer arithmetic */ + char *rp, *wp; /* where to read, where to write */ + int nreaders, nwriters; /* number of openings for r/w */ + struct fasync_struct *async_queue; /* asynchronous readers */ + struct semaphore sem; /* mutual exclusion semaphore */ + struct cdev cdev; /* Char device structure */ +}; + +/* parameters */ +static int scull_p_nr_devs = SCULL_P_NR_DEVS; /* number of pipe devices */ +int scull_p_buffer = SCULL_P_BUFFER; /* buffer size */ +dev_t scull_p_devno; /* Our first device number */ + +module_param(scull_p_nr_devs, int, 0); /* FIXME check perms */ +module_param(scull_p_buffer, int, 0); + +static struct scull_pipe *scull_p_devices; + +static int scull_p_fasync(int fd, struct file *filp, int mode); +static int spacefree(struct scull_pipe *dev); +/* + * Open and close + */ + + +static int scull_p_open(struct inode *inode, struct file *filp) +{ + struct scull_pipe *dev; + + dev = container_of(inode->i_cdev, struct scull_pipe, cdev); + filp->private_data = dev; + + if (down_interruptible(&dev->sem)) + return -ERESTARTSYS; + if (!dev->buffer) { + /* allocate the buffer */ + dev->buffer = kmalloc(scull_p_buffer, GFP_KERNEL); + if (!dev->buffer) { + up(&dev->sem); + return -ENOMEM; + } + } + dev->buffersize = scull_p_buffer; + dev->end = dev->buffer + dev->buffersize; + dev->rp = dev->wp = dev->buffer; /* rd and wr from the beginning */ + + /* use f_mode,not f_flags: it's cleaner (fs/open.c tells why) */ + if (filp->f_mode & FMODE_READ) + dev->nreaders++; + if (filp->f_mode & FMODE_WRITE) + dev->nwriters++; + up(&dev->sem); + + return nonseekable_open(inode, filp); +} + + + +static int scull_p_release(struct inode *inode, struct file *filp) +{ + struct scull_pipe *dev = filp->private_data; + + /* remove this filp from the asynchronously notified filp's */ + scull_p_fasync(-1, filp, 0); + down(&dev->sem); + if (filp->f_mode & FMODE_READ) + dev->nreaders--; + if (filp->f_mode & FMODE_WRITE) + dev->nwriters--; + if (dev->nreaders + dev->nwriters == 0) { + kfree(dev->buffer); + dev->buffer = NULL; /* the other fields are not checked on open */ + } + up(&dev->sem); + return 0; +} + + +/* + * Data management: read and write + */ + +static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + struct scull_pipe *dev = filp->private_data; + + if (down_interruptible(&dev->sem)) + return -ERESTARTSYS; + + while (dev->rp == dev->wp) { /* nothing to read */ + up(&dev->sem); /* release the lock */ + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + PDEBUG("\"%s\" reading: going to sleep\n", current->comm); + if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp))) + return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ + /* otherwise loop, but first reacquire the lock */ + if (down_interruptible(&dev->sem)) + return -ERESTARTSYS; + } + /* ok, data is there, return something */ + if (dev->wp > dev->rp) + count = min(count, (size_t)(dev->wp - dev->rp)); + else /* the write pointer has wrapped, return data up to dev->end */ + count = min(count, (size_t)(dev->end - dev->rp)); + if (copy_to_user(buf, dev->rp, count)) { + up (&dev->sem); + return -EFAULT; + } + dev->rp += count; + if (dev->rp == dev->end) + dev->rp = dev->buffer; /* wrapped */ + up (&dev->sem); + + /* finally, awake any writers and return */ + wake_up_interruptible(&dev->outq); + PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count); + return count; +} + +/* Wait for space for writing; caller must hold device semaphore. On + * error the semaphore will be released before returning. */ +static int scull_getwritespace(struct scull_pipe *dev, struct file *filp) +{ + while (spacefree(dev) == 0) { /* full */ + DEFINE_WAIT(wait); + + up(&dev->sem); + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + PDEBUG("\"%s\" writing: going to sleep\n",current->comm); + prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE); + if (spacefree(dev) == 0) + schedule(); + finish_wait(&dev->outq, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ + if (down_interruptible(&dev->sem)) + return -ERESTARTSYS; + } + return 0; +} + +/* How much space is free? */ +static int spacefree(struct scull_pipe *dev) +{ + if (dev->rp == dev->wp) + return dev->buffersize - 1; + return ((dev->rp + dev->buffersize - dev->wp) % dev->buffersize) - 1; +} + +static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count, + loff_t *f_pos) +{ + struct scull_pipe *dev = filp->private_data; + int result; + + if (down_interruptible(&dev->sem)) + return -ERESTARTSYS; + + /* Make sure there's space to write */ + result = scull_getwritespace(dev, filp); + if (result) + return result; /* scull_getwritespace called up(&dev->sem) */ + + /* ok, space is there, accept something */ + count = min(count, (size_t)spacefree(dev)); + if (dev->wp >= dev->rp) + count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */ + else /* the write pointer has wrapped, fill up to rp-1 */ + count = min(count, (size_t)(dev->rp - dev->wp - 1)); + PDEBUG("Going to accept %li bytes to %p from %p\n", (long)count, dev->wp, buf); + if (copy_from_user(dev->wp, buf, count)) { + up (&dev->sem); + return -EFAULT; + } + dev->wp += count; + if (dev->wp == dev->end) + dev->wp = dev->buffer; /* wrapped */ + up(&dev->sem); + + /* finally, awake any reader */ + wake_up_interruptible(&dev->inq); /* blocked in read() and select() */ + + /* and signal asynchronous readers, explained late in chapter 5 */ + if (dev->async_queue) + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + PDEBUG("\"%s\" did write %li bytes\n",current->comm, (long)count); + return count; +} + +static unsigned int scull_p_poll(struct file *filp, poll_table *wait) +{ + struct scull_pipe *dev = filp->private_data; + unsigned int mask = 0; + + /* + * The buffer is circular; it is considered full + * if "wp" is right behind "rp" and empty if the + * two are equal. + */ + down(&dev->sem); + poll_wait(filp, &dev->inq, wait); + poll_wait(filp, &dev->outq, wait); + if (dev->rp != dev->wp) + mask |= POLLIN | POLLRDNORM; /* readable */ + if (spacefree(dev)) + mask |= POLLOUT | POLLWRNORM; /* writable */ + up(&dev->sem); + return mask; +} + + + + + +static int scull_p_fasync(int fd, struct file *filp, int mode) +{ + struct scull_pipe *dev = filp->private_data; + + return fasync_helper(fd, filp, mode, &dev->async_queue); +} + + + +/* FIXME this should use seq_file */ +#ifdef SCULL_DEBUG +static void scullp_proc_offset(char *buf, char **start, off_t *offset, int *len) +{ + if (*offset == 0) + return; + if (*offset >= *len) { /* Not there yet */ + *offset -= *len; + *len = 0; + } + else { /* We're into the interesting stuff now */ + *start = buf + *offset; + *offset = 0; + } +} + + +static int scull_read_p_mem(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + int i, len; + struct scull_pipe *p; + +#define LIMIT (PAGE_SIZE-200) /* don't print any more after this size */ + *start = buf; + len = sprintf(buf, "Default buffersize is %i\n", scull_p_buffer); + for(i = 0; i<scull_p_nr_devs && len <= LIMIT; i++) { + p = &scull_p_devices[i]; + if (down_interruptible(&p->sem)) + return -ERESTARTSYS; + len += sprintf(buf+len, "\nDevice %i: %p\n", i, p); +/* len += sprintf(buf+len, " Queues: %p %p\n", p->inq, p->outq);*/ + len += sprintf(buf+len, " Buffer: %p to %p (%i bytes)\n", p->buffer, p->end, p->buffersize); + len += sprintf(buf+len, " rp %p wp %p\n", p->rp, p->wp); + len += sprintf(buf+len, " readers %i writers %i\n", p->nreaders, p->nwriters); + up(&p->sem); + scullp_proc_offset(buf, start, &offset, &len); + } + *eof = (len <= LIMIT); + return len; +} + + +#endif + + + +/* + * The file operations for the pipe device + * (some are overlayed with bare scull) + */ +struct file_operations scull_pipe_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = scull_p_read, + .write = scull_p_write, + .poll = scull_p_poll, + .ioctl = scull_ioctl, + .open = scull_p_open, + .release = scull_p_release, + .fasync = scull_p_fasync, +}; + + +/* + * Set up a cdev entry. + */ +static void scull_p_setup_cdev(struct scull_pipe *dev, int index) +{ + int err, devno = scull_p_devno + index; + + cdev_init(&dev->cdev, &scull_pipe_fops); + dev->cdev.owner = THIS_MODULE; + err = cdev_add (&dev->cdev, devno, 1); + /* Fail gracefully if need be */ + if (err) + printk(KERN_NOTICE "Error %d adding scullpipe%d", err, index); +} + + + +/* + * Initialize the pipe devs; return how many we did. + */ +int scull_p_init(dev_t firstdev) +{ + int i, result; + + result = register_chrdev_region(firstdev, scull_p_nr_devs, "scullp"); + if (result < 0) { + printk(KERN_NOTICE "Unable to get scullp region, error %d\n", result); + return 0; + } + scull_p_devno = firstdev; + scull_p_devices = kmalloc(scull_p_nr_devs * sizeof(struct scull_pipe), GFP_KERNEL); + if (scull_p_devices == NULL) { + unregister_chrdev_region(firstdev, scull_p_nr_devs); + return 0; + } + memset(scull_p_devices, 0, scull_p_nr_devs * sizeof(struct scull_pipe)); + for (i = 0; i < scull_p_nr_devs; i++) { + init_waitqueue_head(&(scull_p_devices[i].inq)); + init_waitqueue_head(&(scull_p_devices[i].outq)); + init_MUTEX(&scull_p_devices[i].sem); + scull_p_setup_cdev(scull_p_devices + i, i); + } +#ifdef SCULL_DEBUG + create_proc_read_entry("scullpipe", 0, NULL, scull_read_p_mem, NULL); +#endif + return scull_p_nr_devs; +} + +/* + * This is called by cleanup_module or on failure. + * It is required to never fail, even if nothing was initialized first + */ +void scull_p_cleanup(void) +{ + int i; + +#ifdef SCULL_DEBUG + remove_proc_entry("scullpipe", NULL); +#endif + + if (!scull_p_devices) + return; /* nothing else to release */ + + for (i = 0; i < scull_p_nr_devs; i++) { + cdev_del(&scull_p_devices[i].cdev); + kfree(scull_p_devices[i].buffer); + } + kfree(scull_p_devices); + unregister_chrdev_region(scull_p_devno, scull_p_nr_devs); + scull_p_devices = NULL; /* pedantic */ +} diff --git a/scull/scull.h b/scull/scull.h new file mode 100644 index 0000000..ac7362a --- /dev/null +++ b/scull/scull.h @@ -0,0 +1,177 @@ +/* + * scull.h -- definitions for the char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: scull.h,v 1.15 2004/11/04 17:51:18 rubini Exp $ + */ + +#ifndef _SCULL_H_ +#define _SCULL_H_ + +#include <linux/ioctl.h> /* needed for the _IOW etc stuff used later */ + +/* + * Macros to help debugging + */ + +#undef PDEBUG /* undef it, just in case */ +#ifdef SCULL_DEBUG +# ifdef __KERNEL__ + /* This one if debugging is on, and kernel space */ +# define PDEBUG(fmt, args...) printk( KERN_DEBUG "scull: " fmt, ## args) +# else + /* This one for user space */ +# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) +# endif +#else +# define PDEBUG(fmt, args...) /* not debugging: nothing */ +#endif + +#undef PDEBUGG +#define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */ + +#ifndef SCULL_MAJOR +#define SCULL_MAJOR 0 /* dynamic major by default */ +#endif + +#ifndef SCULL_NR_DEVS +#define SCULL_NR_DEVS 4 /* scull0 through scull3 */ +#endif + +#ifndef SCULL_P_NR_DEVS +#define SCULL_P_NR_DEVS 4 /* scullpipe0 through scullpipe3 */ +#endif + +/* + * The bare device is a variable-length region of memory. + * Use a linked list of indirect blocks. + * + * "scull_dev->data" points to an array of pointers, each + * pointer refers to a memory area of SCULL_QUANTUM bytes. + * + * The array (quantum-set) is SCULL_QSET long. + */ +#ifndef SCULL_QUANTUM +#define SCULL_QUANTUM 4000 +#endif + +#ifndef SCULL_QSET +#define SCULL_QSET 1000 +#endif + +/* + * The pipe device is a simple circular buffer. Here its default size + */ +#ifndef SCULL_P_BUFFER +#define SCULL_P_BUFFER 4000 +#endif + +/* + * Representation of scull quantum sets. + */ +struct scull_qset { + void **data; + struct scull_qset *next; +}; + +struct scull_dev { + struct scull_qset *data; /* Pointer to first quantum set */ + int quantum; /* the current quantum size */ + int qset; /* the current array size */ + unsigned long size; /* amount of data stored here */ + unsigned int access_key; /* used by sculluid and scullpriv */ + struct semaphore sem; /* mutual exclusion semaphore */ + struct cdev cdev; /* Char device structure */ +}; + +/* + * Split minors in two parts + */ +#define TYPE(minor) (((minor) >> 4) & 0xf) /* high nibble */ +#define NUM(minor) ((minor) & 0xf) /* low nibble */ + + +/* + * The different configurable parameters + */ +extern int scull_major; /* main.c */ +extern int scull_nr_devs; +extern int scull_quantum; +extern int scull_qset; + +extern int scull_p_buffer; /* pipe.c */ + + +/* + * Prototypes for shared functions + */ + +int scull_p_init(dev_t dev); +void scull_p_cleanup(void); +int scull_access_init(dev_t dev); +void scull_access_cleanup(void); + +int scull_trim(struct scull_dev *dev); + +ssize_t scull_read(struct file *filp, char __user *buf, size_t count, + loff_t *f_pos); +ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, + loff_t *f_pos); +loff_t scull_llseek(struct file *filp, loff_t off, int whence); +int scull_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg); + + +/* + * Ioctl definitions + */ + +/* Use 'k' as magic number */ +#define SCULL_IOC_MAGIC 'k' +/* Please use a different 8-bit number in your code */ + +#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0) + +/* + * S means "Set" through a ptr, + * T means "Tell" directly with the argument value + * G means "Get": reply by setting through a pointer + * Q means "Query": response is on the return value + * X means "eXchange": switch G and S atomically + * H means "sHift": switch T and Q atomically + */ +#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int) +#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int) +#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3) +#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4) +#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int) +#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int) +#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7) +#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8) +#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int) +#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int) +#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11) +#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12) + +/* + * The other entities only have "Tell" and "Query", because they're + * not printed in the book, and there's no need to have all six. + * (The previous stuff was only there to show different ways to do it. + */ +#define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC, 13) +#define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC, 14) +/* ... more to come */ + +#define SCULL_IOC_MAXNR 14 + +#endif /* _SCULL_H_ */ diff --git a/scull/scull.init b/scull/scull.init new file mode 100644 index 0000000..e0523ce --- /dev/null +++ b/scull/scull.init @@ -0,0 +1,142 @@ +#!/bin/bash +# Sample init script for the a driver module <rubini@linux.it> + +DEVICE="scull" +SECTION="misc" + +# The list of filenames and minor numbers: $PREFIX is prefixed to all names +PREFIX="scull" +FILES=" 0 0 1 1 2 2 3 3 priv 16 + pipe0 32 pipe1 33 pipe2 34 pipe3 35 + single 48 uid 64 wuid 80" + +INSMOD=/sbin/insmod; # use /sbin/modprobe if you prefer + +function device_specific_post_load () { + true; # fill at will +} +function device_specific_pre_unload () { + true; # fill at will +} + +# Everything below this line should work unchanged for any char device. +# Obviously, however, no options on the command line: either in +# /etc/${DEVICE}.conf or /etc/modules.conf (if modprobe is used) + +# Optional configuration file: format is +# owner <ownername> +# group <groupname> +# mode <modename> +# options <insmod options> +CFG=/etc/${DEVICE}.conf + +# kernel version, used to look for modules +KERNEL=`uname -r` + +#FIXME: it looks like there is no misc section. Where should it be? +MODDIR="/lib/modules/${KERNEL}/kernel/drivers/${SECTION}" +if [ ! -d $MODDIR ]; then MODDIR="/lib/modules/${KERNEL}/${SECTION}"; fi + +# Root or die +if [ "$(id -u)" != "0" ] +then + echo "You must be root to load or unload kernel modules" + exit 1 +fi + +# Read configuration file +if [ -r $CFG ]; then + OWNER=`awk "\\$1==\"owner\" {print \\$2}" $CFG` + GROUP=`awk "\\$1==\"group\" {print \\$2}" $CFG` + MODE=`awk "\\$1==\"mode\" {print \\$2}" $CFG` + # The options string may include extra blanks or only blanks + OPTIONS=`sed -n '/^options / s/options //p' $CFG` +fi + + +# Create device files +function create_files () { + cd /dev + local devlist="" + local file + while true; do + if [ $# -lt 2 ]; then break; fi + file="${DEVICE}$1" + mknod $file c $MAJOR $2 + devlist="$devlist $file" + shift 2 + done + if [ -n "$OWNER" ]; then chown $OWNER $devlist; fi + if [ -n "$GROUP" ]; then chgrp $GROUP $devlist; fi + if [ -n "$MODE" ]; then chmod $MODE $devlist; fi +} + +# Remove device files +function remove_files () { + cd /dev + local devlist="" + local file + while true; do + if [ $# -lt 2 ]; then break; fi + file="${DEVICE}$1" + devlist="$devlist $file" + shift 2 + done + rm -f $devlist +} + +# Load and create files +function load_device () { + + if [ -f $MODDIR/$DEVICE.o ]; then + devpath=$MODDIR/$DEVICE.o + else if [ -f ./$DEVICE.o ]; then + devpath=./$DEVICE.o + else + devpath=$DEVICE; # let insmod/modprobe guess + fi; fi + if [ "$devpath" != "$DEVICE" ]; then + echo -n " (loading file $devpath)" + fi + + if $INSMOD $devpath $OPTIONS; then + MAJOR=`awk "\\$2==\"$DEVICE\" {print \\$1}" /proc/devices` + remove_files $FILES + create_files $FILES + device_specific_post_load + else + echo " FAILED!" + fi +} + +# Unload and remove files +function unload_device () { + device_specific_pre_unload + /sbin/rmmod $DEVICE + remove_files $FILES +} + + +case "$1" in + start) + echo -n "Loading $DEVICE" + load_device + echo "." + ;; + stop) + echo -n "Unloading $DEVICE" + unload_device + echo "." + ;; + force-reload|restart) + echo -n "Reloading $DEVICE" + unload_device + load_device + echo "." + ;; + *) + echo "Usage: $0 {start|stop|restart|force-reload}" + exit 1 +esac + +exit 0 diff --git a/scull/scull_load b/scull/scull_load new file mode 100644 index 0000000..0da1378 --- /dev/null +++ b/scull/scull_load @@ -0,0 +1,66 @@ +#!/bin/sh +# $Id: scull_load,v 1.4 2004/11/03 06:19:49 rubini Exp $ +module="scull" +device="scull" +mode="664" + +# Group: since distributions do it differently, look for wheel or use staff +if grep -q '^staff:' /etc/group; then + group="staff" +else + group="wheel" +fi + +# invoke insmod with all arguments we got +# and use a pathname, as insmod doesn't look in . by default +/sbin/insmod ./$module.ko $* || exit 1 + +# retrieve major number +major=$(awk "\$2==\"$module\" {print \$1}" /proc/devices) + +# Remove stale nodes and replace them, then give gid and perms +# Usually the script is shorter, it's scull that has several devices in it. + +rm -f /dev/${device}[0-3] +mknod /dev/${device}0 c $major 0 +mknod /dev/${device}1 c $major 1 +mknod /dev/${device}2 c $major 2 +mknod /dev/${device}3 c $major 3 +ln -sf ${device}0 /dev/${device} +chgrp $group /dev/${device}[0-3] +chmod $mode /dev/${device}[0-3] + +rm -f /dev/${device}pipe[0-3] +mknod /dev/${device}pipe0 c $major 4 +mknod /dev/${device}pipe1 c $major 5 +mknod /dev/${device}pipe2 c $major 6 +mknod /dev/${device}pipe3 c $major 7 +ln -sf ${device}pipe0 /dev/${device}pipe +chgrp $group /dev/${device}pipe[0-3] +chmod $mode /dev/${device}pipe[0-3] + +rm -f /dev/${device}single +mknod /dev/${device}single c $major 8 +chgrp $group /dev/${device}single +chmod $mode /dev/${device}single + +rm -f /dev/${device}uid +mknod /dev/${device}uid c $major 9 +chgrp $group /dev/${device}uid +chmod $mode /dev/${device}uid + +rm -f /dev/${device}wuid +mknod /dev/${device}wuid c $major 10 +chgrp $group /dev/${device}wuid +chmod $mode /dev/${device}wuid + +rm -f /dev/${device}priv +mknod /dev/${device}priv c $major 11 +chgrp $group /dev/${device}priv +chmod $mode /dev/${device}priv + + + + + + diff --git a/scull/scull_unload b/scull/scull_unload new file mode 100644 index 0000000..269a0aa --- /dev/null +++ b/scull/scull_unload @@ -0,0 +1,20 @@ +#!/bin/sh +module="scull" +device="scull" + +# invoke rmmod with all arguments we got +/sbin/rmmod $module $* || exit 1 + +# Remove stale nodes + +rm -f /dev/${device} /dev/${device}[0-3] +rm -f /dev/${device}priv +rm -f /dev/${device}pipe /dev/${device}pipe[0-3] +rm -f /dev/${device}single +rm -f /dev/${device}uid +rm -f /dev/${device}wuid + + + + + diff --git a/scullc/Makefile b/scullc/Makefile new file mode 100644 index 0000000..9b90b41 --- /dev/null +++ b/scullc/Makefile @@ -0,0 +1,46 @@ + +# Comment/uncomment the following line to enable/disable debugging +#DEBUG = y + + +ifeq ($(DEBUG),y) + DEBFLAGS = -O -g -DSCULLC_DEBUG # "-O" is needed to expand inlines +else + DEBFLAGS = -O2 +endif + +CFLAGS += $(DEBFLAGS) -I$(LDDINC) + +TARGET = scullc + +ifneq ($(KERNELRELEASE),) + +scullc-objs := main.o + +obj-m := scullc.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +modules: + $(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD) modules + +endif + + +install: + install -d $(INSTALLDIR) + install -c $(TARGET).o $(INSTALLDIR) + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/scullc/main.c b/scullc/main.c new file mode 100644 index 0000000..f7a1ca5 --- /dev/null +++ b/scullc/main.c @@ -0,0 +1,600 @@ +/* -*- C -*- + * main.c -- the bare scullc char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: _main.c.in,v 1.21 2004/10/14 20:11:39 corbet Exp $ + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/slab.h> /* kmalloc() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/types.h> /* size_t */ +#include <linux/proc_fs.h> +#include <linux/fcntl.h> /* O_ACCMODE */ +#include <linux/aio.h> +#include <asm/uaccess.h> +#include "scullc.h" /* local definitions */ + + +int scullc_major = SCULLC_MAJOR; +int scullc_devs = SCULLC_DEVS; /* number of bare scullc devices */ +int scullc_qset = SCULLC_QSET; +int scullc_quantum = SCULLC_QUANTUM; + +module_param(scullc_major, int, 0); +module_param(scullc_devs, int, 0); +module_param(scullc_qset, int, 0); +module_param(scullc_quantum, int, 0); +MODULE_AUTHOR("Alessandro Rubini"); +MODULE_LICENSE("Dual BSD/GPL"); + +struct scullc_dev *scullc_devices; /* allocated in scullc_init */ + +int scullc_trim(struct scullc_dev *dev); +void scullc_cleanup(void); + +/* declare one cache pointer: use it for all devices */ +kmem_cache_t *scullc_cache; + + + + + +#ifdef SCULLC_USE_PROC /* don't waste space if unused */ +/* + * The proc filesystem: function to read and entry + */ + +void scullc_proc_offset(char *buf, char **start, off_t *offset, int *len) +{ + if (*offset == 0) + return; + if (*offset >= *len) { + /* Not there yet */ + *offset -= *len; + *len = 0; + } else { + /* We're into the interesting stuff now */ + *start = buf + *offset; + *offset = 0; + } +} + +/* FIXME: Do we need this here?? It be ugly */ +int scullc_read_procmem(char *buf, char **start, off_t offset, + int count, int *eof, void *data) +{ + int i, j, quantum, qset, len = 0; + int limit = count - 80; /* Don't print more than this */ + struct scullc_dev *d; + + *start = buf; + for(i = 0; i < scullc_devs; i++) { + d = &scullc_devices[i]; + if (down_interruptible (&d->sem)) + return -ERESTARTSYS; + qset = d->qset; /* retrieve the features of each device */ + quantum=d->quantum; + len += sprintf(buf+len,"\nDevice %i: qset %i, quantum %i, sz %li\n", + i, qset, quantum, (long)(d->size)); + for (; d; d = d->next) { /* scan the list */ + len += sprintf(buf+len," item at %p, qset at %p\n",d,d->data); + scullc_proc_offset (buf, start, &offset, &len); + if (len > limit) + goto out; + if (d->data && !d->next) /* dump only the last item - save space */ + for (j = 0; j < qset; j++) { + if (d->data[j]) + len += sprintf(buf+len," % 4i:%8p\n",j,d->data[j]); + scullc_proc_offset (buf, start, &offset, &len); + if (len > limit) + goto out; + } + } + out: + up (&scullc_devices[i].sem); + if (len > limit) + break; + } + *eof = 1; + return len; +} + +#endif /* SCULLC_USE_PROC */ + +/* + * Open and close + */ + +int scullc_open (struct inode *inode, struct file *filp) +{ + struct scullc_dev *dev; /* device information */ + + /* Find the device */ + dev = container_of(inode->i_cdev, struct scullc_dev, cdev); + + /* now trim to 0 the length of the device if open was write-only */ + if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) { + if (down_interruptible (&dev->sem)) + return -ERESTARTSYS; + scullc_trim(dev); /* ignore errors */ + up (&dev->sem); + } + + /* and use filp->private_data to point to the device data */ + filp->private_data = dev; + + return 0; /* success */ +} + +int scullc_release (struct inode *inode, struct file *filp) +{ + return 0; +} + +/* + * Follow the list + */ +struct scullc_dev *scullc_follow(struct scullc_dev *dev, int n) +{ + while (n--) { + if (!dev->next) { + dev->next = kmalloc(sizeof(struct scullc_dev), GFP_KERNEL); + memset(dev->next, 0, sizeof(struct scullc_dev)); + } + dev = dev->next; + continue; + } + return dev; +} + +/* + * Data management: read and write + */ + +ssize_t scullc_read (struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + struct scullc_dev *dev = filp->private_data; /* the first listitem */ + struct scullc_dev *dptr; + int quantum = dev->quantum; + int qset = dev->qset; + int itemsize = quantum * qset; /* how many bytes in the listitem */ + int item, s_pos, q_pos, rest; + ssize_t retval = 0; + + if (down_interruptible (&dev->sem)) + return -ERESTARTSYS; + if (*f_pos > dev->size) + goto nothing; + if (*f_pos + count > dev->size) + count = dev->size - *f_pos; + /* find listitem, qset index, and offset in the quantum */ + item = ((long) *f_pos) / itemsize; + rest = ((long) *f_pos) % itemsize; + s_pos = rest / quantum; q_pos = rest % quantum; + + /* follow the list up to the right position (defined elsewhere) */ + dptr = scullc_follow(dev, item); + + if (!dptr->data) + goto nothing; /* don't fill holes */ + if (!dptr->data[s_pos]) + goto nothing; + if (count > quantum - q_pos) + count = quantum - q_pos; /* read only up to the end of this quantum */ + + if (copy_to_user (buf, dptr->data[s_pos]+q_pos, count)) { + retval = -EFAULT; + goto nothing; + } + up (&dev->sem); + + *f_pos += count; + return count; + + nothing: + up (&dev->sem); + return retval; +} + + + +ssize_t scullc_write (struct file *filp, const char __user *buf, size_t count, + loff_t *f_pos) +{ + struct scullc_dev *dev = filp->private_data; + struct scullc_dev *dptr; + int quantum = dev->quantum; + int qset = dev->qset; + int itemsize = quantum * qset; + int item, s_pos, q_pos, rest; + ssize_t retval = -ENOMEM; /* our most likely error */ + + if (down_interruptible (&dev->sem)) + return -ERESTARTSYS; + + /* find listitem, qset index and offset in the quantum */ + item = ((long) *f_pos) / itemsize; + rest = ((long) *f_pos) % itemsize; + s_pos = rest / quantum; q_pos = rest % quantum; + + /* follow the list up to the right position */ + dptr = scullc_follow(dev, item); + if (!dptr->data) { + dptr->data = kmalloc(qset * sizeof(void *), GFP_KERNEL); + if (!dptr->data) + goto nomem; + memset(dptr->data, 0, qset * sizeof(char *)); + } + /* Allocate a quantum using the memory cache */ + if (!dptr->data[s_pos]) { + dptr->data[s_pos] = kmem_cache_alloc(scullc_cache, GFP_KERNEL); + if (!dptr->data[s_pos]) + goto nomem; + memset(dptr->data[s_pos], 0, scullc_quantum); + } + if (count > quantum - q_pos) + count = quantum - q_pos; /* write only up to the end of this quantum */ + if (copy_from_user (dptr->data[s_pos]+q_pos, buf, count)) { + retval = -EFAULT; + goto nomem; + } + *f_pos += count; + + /* update the size */ + if (dev->size < *f_pos) + dev->size = *f_pos; + up (&dev->sem); + return count; + + nomem: + up (&dev->sem); + return retval; +} + +/* + * The ioctl() implementation + */ + +int scullc_ioctl (struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + + int err = 0, ret = 0, tmp; + + /* don't even decode wrong cmds: better returning ENOTTY than EFAULT */ + if (_IOC_TYPE(cmd) != SCULLC_IOC_MAGIC) return -ENOTTY; + if (_IOC_NR(cmd) > SCULLC_IOC_MAXNR) return -ENOTTY; + + /* + * the type is a bitmask, and VERIFY_WRITE catches R/W + * transfers. Note that the type is user-oriented, while + * verify_area is kernel-oriented, so the concept of "read" and + * "write" is reversed + */ + if (_IOC_DIR(cmd) & _IOC_READ) + err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); + else if (_IOC_DIR(cmd) & _IOC_WRITE) + err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); + if (err) + return -EFAULT; + + switch(cmd) { + + case SCULLC_IOCRESET: + scullc_qset = SCULLC_QSET; + scullc_quantum = SCULLC_QUANTUM; + break; + + case SCULLC_IOCSQUANTUM: /* Set: arg points to the value */ + ret = __get_user(scullc_quantum, (int __user *) arg); + break; + + case SCULLC_IOCTQUANTUM: /* Tell: arg is the value */ + scullc_quantum = arg; + break; + + case SCULLC_IOCGQUANTUM: /* Get: arg is pointer to result */ + ret = __put_user (scullc_quantum, (int __user *) arg); + break; + + case SCULLC_IOCQQUANTUM: /* Query: return it (it's positive) */ + return scullc_quantum; + + case SCULLC_IOCXQUANTUM: /* eXchange: use arg as pointer */ + tmp = scullc_quantum; + ret = __get_user(scullc_quantum, (int __user *) arg); + if (ret == 0) + ret = __put_user(tmp, (int __user *) arg); + break; + + case SCULLC_IOCHQUANTUM: /* sHift: like Tell + Query */ + tmp = scullc_quantum; + scullc_quantum = arg; + return tmp; + + case SCULLC_IOCSQSET: + ret = __get_user(scullc_qset, (int __user *) arg); + break; + + case SCULLC_IOCTQSET: + scullc_qset = arg; + break; + + case SCULLC_IOCGQSET: + ret = __put_user(scullc_qset, (int __user *)arg); + break; + + case SCULLC_IOCQQSET: + return scullc_qset; + + case SCULLC_IOCXQSET: + tmp = scullc_qset; + ret = __get_user(scullc_qset, (int __user *)arg); + if (ret == 0) + ret = __put_user(tmp, (int __user *)arg); + break; + + case SCULLC_IOCHQSET: + tmp = scullc_qset; + scullc_qset = arg; + return tmp; + + default: /* redundant, as cmd was checked against MAXNR */ + return -ENOTTY; + } + + return ret; +} + +/* + * The "extended" operations + */ + +loff_t scullc_llseek (struct file *filp, loff_t off, int whence) +{ + struct scullc_dev *dev = filp->private_data; + long newpos; + + switch(whence) { + case 0: /* SEEK_SET */ + newpos = off; + break; + + case 1: /* SEEK_CUR */ + newpos = filp->f_pos + off; + break; + + case 2: /* SEEK_END */ + newpos = dev->size + off; + break; + + default: /* can't happen */ + return -EINVAL; + } + if (newpos<0) return -EINVAL; + filp->f_pos = newpos; + return newpos; +} + + +/* + * A simple asynchronous I/O implementation. + */ + +struct async_work { + struct kiocb *iocb; + int result; + struct work_struct work; +}; + +/* + * "Complete" an asynchronous operation. + */ +static void scullc_do_deferred_op(void *p) +{ + struct async_work *stuff = (struct async_work *) p; + aio_complete(stuff->iocb, stuff->result, 0); + kfree(stuff); +} + + +static int scullc_defer_op(int write, struct kiocb *iocb, char __user *buf, + size_t count, loff_t pos) +{ + struct async_work *stuff; + int result; + + /* Copy now while we can access the buffer */ + if (write) + result = scullc_write(iocb->ki_filp, buf, count, &pos); + else + result = scullc_read(iocb->ki_filp, buf, count, &pos); + + /* If this is a synchronous IOCB, we return our status now. */ + if (is_sync_kiocb(iocb)) + return result; + + /* Otherwise defer the completion for a few milliseconds. */ + stuff = kmalloc (sizeof (*stuff), GFP_KERNEL); + if (stuff == NULL) + return result; /* No memory, just complete now */ + stuff->iocb = iocb; + stuff->result = result; + INIT_WORK(&stuff->work, scullc_do_deferred_op, stuff); + schedule_delayed_work(&stuff->work, HZ/100); + return -EIOCBQUEUED; +} + + +static ssize_t scullc_aio_read(struct kiocb *iocb, char __user *buf, size_t count, + loff_t pos) +{ + return scullc_defer_op(0, iocb, buf, count, pos); +} + +static ssize_t scullc_aio_write(struct kiocb *iocb, const char __user *buf, + size_t count, loff_t pos) +{ + return scullc_defer_op(1, iocb, (char __user *) buf, count, pos); +} + + + + +/* + * The fops + */ + +struct file_operations scullc_fops = { + .owner = THIS_MODULE, + .llseek = scullc_llseek, + .read = scullc_read, + .write = scullc_write, + .ioctl = scullc_ioctl, + .open = scullc_open, + .release = scullc_release, + .aio_read = scullc_aio_read, + .aio_write = scullc_aio_write, +}; + +int scullc_trim(struct scullc_dev *dev) +{ + struct scullc_dev *next, *dptr; + int qset = dev->qset; /* "dev" is not-null */ + int i; + + if (dev->vmas) /* don't trim: there are active mappings */ + return -EBUSY; + + for (dptr = dev; dptr; dptr = next) { /* all the list items */ + if (dptr->data) { + for (i = 0; i < qset; i++) + if (dptr->data[i]) + kmem_cache_free(scullc_cache, dptr->data[i]); + + kfree(dptr->data); + dptr->data=NULL; + } + next=dptr->next; + if (dptr != dev) kfree(dptr); /* all of them but the first */ + } + dev->size = 0; + dev->qset = scullc_qset; + dev->quantum = scullc_quantum; + dev->next = NULL; + return 0; +} + + +static void scullc_setup_cdev(struct scullc_dev *dev, int index) +{ + int err, devno = MKDEV(scullc_major, index); + + cdev_init(&dev->cdev, &scullc_fops); + dev->cdev.owner = THIS_MODULE; + dev->cdev.ops = &scullc_fops; + err = cdev_add (&dev->cdev, devno, 1); + /* Fail gracefully if need be */ + if (err) + printk(KERN_NOTICE "Error %d adding scull%d", err, index); +} + + + +/* + * Finally, the module stuff + */ + +int scullc_init(void) +{ + int result, i; + dev_t dev = MKDEV(scullc_major, 0); + + /* + * Register your major, and accept a dynamic number. + */ + if (scullc_major) + result = register_chrdev_region(dev, scullc_devs, "scullc"); + else { + result = alloc_chrdev_region(&dev, 0, scullc_devs, "scullc"); + scullc_major = MAJOR(dev); + } + if (result < 0) + return result; + + + /* + * allocate the devices -- we can't have them static, as the number + * can be specified at load time + */ + scullc_devices = kmalloc(scullc_devs*sizeof (struct scullc_dev), GFP_KERNEL); + if (!scullc_devices) { + result = -ENOMEM; + goto fail_malloc; + } + memset(scullc_devices, 0, scullc_devs*sizeof (struct scullc_dev)); + for (i = 0; i < scullc_devs; i++) { + scullc_devices[i].quantum = scullc_quantum; + scullc_devices[i].qset = scullc_qset; + sema_init (&scullc_devices[i].sem, 1); + scullc_setup_cdev(scullc_devices + i, i); + } + + scullc_cache = kmem_cache_create("scullc", scullc_quantum, + 0, SLAB_HWCACHE_ALIGN, NULL, NULL); /* no ctor/dtor */ + if (!scullc_cache) { + scullc_cleanup(); + return -ENOMEM; + } + +#ifdef SCULLC_USE_PROC /* only when available */ + create_proc_read_entry("scullcmem", 0, NULL, scullc_read_procmem, NULL); +#endif + return 0; /* succeed */ + + fail_malloc: + unregister_chrdev_region(dev, scullc_devs); + return result; +} + + + +void scullc_cleanup(void) +{ + int i; + +#ifdef SCULLC_USE_PROC + remove_proc_entry("scullcmem", NULL); +#endif + + for (i = 0; i < scullc_devs; i++) { + cdev_del(&scullc_devices[i].cdev); + scullc_trim(scullc_devices + i); + } + kfree(scullc_devices); + + if (scullc_cache) + kmem_cache_destroy(scullc_cache); + unregister_chrdev_region(MKDEV (scullc_major, 0), scullc_devs); +} + + +module_init(scullc_init); +module_exit(scullc_cleanup); diff --git a/scullc/mmap.c b/scullc/mmap.c new file mode 100644 index 0000000..841f62c --- /dev/null +++ b/scullc/mmap.c @@ -0,0 +1,118 @@ +/* -*- C -*- + * mmap.c -- memory mapping for the scullc char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: _mmap.c.in,v 1.13 2004/10/18 18:07:36 corbet Exp $ + */ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/mm.h> /* everything */ +#include <linux/errno.h> /* error codes */ +#include <asm/pgtable.h> + +#include "scullc.h" /* local definitions */ + + +/* + * open and close: just keep track of how many times the device is + * mapped, to avoid releasing it. + */ + +void scullc_vma_open(struct vm_area_struct *vma) +{ + struct scullc_dev *dev = vma->vm_private_data; + + dev->vmas++; +} + +void scullc_vma_close(struct vm_area_struct *vma) +{ + struct scullc_dev *dev = vma->vm_private_data; + + dev->vmas--; +} + +/* + * The nopage method: the core of the file. It retrieves the + * page required from the scullc device and returns it to the + * user. The count for the page must be incremented, because + * it is automatically decremented at page unmap. + * + * For this reason, "order" must be zero. Otherwise, only the first + * page has its count incremented, and the allocating module must + * release it as a whole block. Therefore, it isn't possible to map + * pages from a multipage block: when they are unmapped, their count + * is individually decreased, and would drop to 0. + */ + +struct page *scullc_vma_nopage(struct vm_area_struct *vma, + unsigned long address, int *type) +{ + unsigned long offset; + struct scullc_dev *ptr, *dev = vma->vm_private_data; + struct page *page = NOPAGE_SIGBUS; + void *pageptr = NULL; /* default to "missing" */ + + down(&dev->sem); + offset = (address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT); + if (offset >= dev->size) goto out; /* out of range */ + + /* + * Now retrieve the scullc device from the list,then the page. + * If the device has holes, the process receives a SIGBUS when + * accessing the hole. + */ + offset >>= PAGE_SHIFT; /* offset is a number of pages */ + for (ptr = dev; ptr && offset >= dev->qset;) { + ptr = ptr->next; + offset -= dev->qset; + } + if (ptr && ptr->data) pageptr = ptr->data[offset]; + if (!pageptr) goto out; /* hole or end-of-file */ + + /* got it, now increment the count */ + get_page(page); + if (type) + *type = VM_FAULT_MINOR; + out: + up(&dev->sem); + return page; +} + + + +struct vm_operations_struct scullc_vm_ops = { + .open = scullc_vma_open, + .close = scullc_vma_close, + .nopage = scullc_vma_nopage, +}; + + +int scullc_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct inode *inode = filp->f_dentry->d_inode; + + /* refuse to map if order is not 0 */ + if (scullc_devices[iminor(inode)].order) + return -ENODEV; + + /* don't do anything here: "nopage" will set up page table entries */ + vma->vm_ops = &scullc_vm_ops; + vma->vm_flags |= VM_RESERVED; + vma->vm_private_data = filp->private_data; + scullc_vma_open(vma); + return 0; +} + diff --git a/scullc/scullc.h b/scullc/scullc.h new file mode 100644 index 0000000..86d5090 --- /dev/null +++ b/scullc/scullc.h @@ -0,0 +1,122 @@ +/* -*- C -*- + * scullc.h -- definitions for the scullc char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + */ + +#include <linux/ioctl.h> +#include <linux/cdev.h> + +/* + * Macros to help debugging + */ + +#undef PDEBUG /* undef it, just in case */ +#ifdef SCULLC_DEBUG +# ifdef __KERNEL__ + /* This one if debugging is on, and kernel space */ +# define PDEBUG(fmt, args...) printk( KERN_DEBUG "scullc: " fmt, ## args) +# else + /* This one for user space */ +# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) +# endif +#else +# define PDEBUG(fmt, args...) /* not debugging: nothing */ +#endif + +#undef PDEBUGG +#define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */ + +#define SCULLC_MAJOR 0 /* dynamic major by default */ + +#define SCULLC_DEVS 4 /* scullc0 through scullc3 */ + +/* + * The bare device is a variable-length region of memory. + * Use a linked list of indirect blocks. + * + * "scullc_dev->data" points to an array of pointers, each + * pointer refers to a memory page. + * + * The array (quantum-set) is SCULLC_QSET long. + */ +#define SCULLC_QUANTUM 4000 /* use a quantum size like scull */ +#define SCULLC_QSET 500 + +struct scullc_dev { + void **data; + struct scullc_dev *next; /* next listitem */ + int vmas; /* active mappings */ + int quantum; /* the current allocation size */ + int qset; /* the current array size */ + size_t size; /* 32-bit will suffice */ + struct semaphore sem; /* Mutual exclusion */ + struct cdev cdev; +}; + +extern struct scullc_dev *scullc_devices; + +extern struct file_operations scullc_fops; + +/* + * The different configurable parameters + */ +extern int scullc_major; /* main.c */ +extern int scullc_devs; +extern int scullc_order; +extern int scullc_qset; + +/* + * Prototypes for shared functions + */ +int scullc_trim(struct scullc_dev *dev); +struct scullc_dev *scullc_follow(struct scullc_dev *dev, int n); + + +#ifdef SCULLC_DEBUG +# define SCULLC_USE_PROC +#endif + +/* + * Ioctl definitions + */ + +/* Use 'K' as magic number */ +#define SCULLC_IOC_MAGIC 'K' + +#define SCULLC_IOCRESET _IO(SCULLC_IOC_MAGIC, 0) + +/* + * S means "Set" through a ptr, + * T means "Tell" directly + * G means "Get" (to a pointed var) + * Q means "Query", response is on the return value + * X means "eXchange": G and S atomically + * H means "sHift": T and Q atomically + */ +#define SCULLC_IOCSQUANTUM _IOW(SCULLC_IOC_MAGIC, 1, int) +#define SCULLC_IOCTQUANTUM _IO(SCULLC_IOC_MAGIC, 2) +#define SCULLC_IOCGQUANTUM _IOR(SCULLC_IOC_MAGIC, 3, int) +#define SCULLC_IOCQQUANTUM _IO(SCULLC_IOC_MAGIC, 4) +#define SCULLC_IOCXQUANTUM _IOWR(SCULLC_IOC_MAGIC, 5, int) +#define SCULLC_IOCHQUANTUM _IO(SCULLC_IOC_MAGIC, 6) +#define SCULLC_IOCSQSET _IOW(SCULLC_IOC_MAGIC, 7, int) +#define SCULLC_IOCTQSET _IO(SCULLC_IOC_MAGIC, 8) +#define SCULLC_IOCGQSET _IOR(SCULLC_IOC_MAGIC, 9, int) +#define SCULLC_IOCQQSET _IO(SCULLC_IOC_MAGIC, 10) +#define SCULLC_IOCXQSET _IOWR(SCULLC_IOC_MAGIC,11, int) +#define SCULLC_IOCHQSET _IO(SCULLC_IOC_MAGIC, 12) + +#define SCULLC_IOC_MAXNR 12 + + + diff --git a/scullc/scullc_load b/scullc/scullc_load new file mode 100644 index 0000000..8ca1f69 --- /dev/null +++ b/scullc/scullc_load @@ -0,0 +1,30 @@ +#!/bin/sh +module="scullc" +device="scullc" +mode="664" + +# Group: since distributions do it differently, look for wheel or use staff +if grep '^staff:' /etc/group > /dev/null; then + group="staff" +else + group="wheel" +fi + +# remove stale nodes +rm -f /dev/${device}? + +# invoke insmod with all arguments we got +# and use a pathname, as newer modutils don't look in . by default +/sbin/insmod -f ./$module.ko $* || exit 1 + +major=`cat /proc/devices | awk "\\$2==\"$module\" {print \\$1}"` + +mknod /dev/${device}0 c $major 0 +mknod /dev/${device}1 c $major 1 +mknod /dev/${device}2 c $major 2 +mknod /dev/${device}3 c $major 3 +ln -sf ${device}0 /dev/${device} + +# give appropriate group/permissions +chgrp $group /dev/${device}[0-3] +chmod $mode /dev/${device}[0-3] diff --git a/scullc/scullc_unload b/scullc/scullc_unload new file mode 100644 index 0000000..bb91f71 --- /dev/null +++ b/scullc/scullc_unload @@ -0,0 +1,11 @@ +#!/bin/sh +module="scullc" +device="scullc" + +# invoke rmmod with all arguments we got +/sbin/rmmod $module $* || exit 1 + +# remove nodes +rm -f /dev/${device}[0-3] /dev/${device} + +exit 0 diff --git a/sculld/Makefile b/sculld/Makefile new file mode 100644 index 0000000..29e9fc3 --- /dev/null +++ b/sculld/Makefile @@ -0,0 +1,46 @@ + +# Comment/uncomment the following line to enable/disable debugging +#DEBUG = y + + +ifeq ($(DEBUG),y) + DEBFLAGS = -O -g -DSCULLD_DEBUG # "-O" is needed to expand inlines +else + DEBFLAGS = -O2 +endif + +CFLAGS += $(DEBFLAGS) -I$(LDDINC) + +TARGET = sculld + +ifneq ($(KERNELRELEASE),) + +sculld-objs := main.o mmap.o + +obj-m := sculld.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +modules: + $(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD) modules + +endif + + +install: + install -d $(INSTALLDIR) + install -c $(TARGET).o $(INSTALLDIR) + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/sculld/main.c b/sculld/main.c new file mode 100644 index 0000000..a4854a5 --- /dev/null +++ b/sculld/main.c @@ -0,0 +1,632 @@ +/* -*- C -*- + * main.c -- the bare sculld char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: _main.c.in,v 1.21 2004/10/14 20:11:39 corbet Exp $ + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/slab.h> /* kmalloc() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/types.h> /* size_t */ +#include <linux/proc_fs.h> +#include <linux/fcntl.h> /* O_ACCMODE */ +#include <linux/aio.h> +#include <asm/uaccess.h> +#include "sculld.h" /* local definitions */ + + +int sculld_major = SCULLD_MAJOR; +int sculld_devs = SCULLD_DEVS; /* number of bare sculld devices */ +int sculld_qset = SCULLD_QSET; +int sculld_order = SCULLD_ORDER; + +module_param(sculld_major, int, 0); +module_param(sculld_devs, int, 0); +module_param(sculld_qset, int, 0); +module_param(sculld_order, int, 0); +MODULE_AUTHOR("Alessandro Rubini"); +MODULE_LICENSE("Dual BSD/GPL"); + +struct sculld_dev *sculld_devices; /* allocated in sculld_init */ + +int sculld_trim(struct sculld_dev *dev); +void sculld_cleanup(void); + + + +/* Device model stuff */ + +static struct ldd_driver sculld_driver = { + .version = "$Revision: 1.21 $", + .module = THIS_MODULE, + .driver = { + .name = "sculld", + }, +}; + + + +#ifdef SCULLD_USE_PROC /* don't waste space if unused */ +/* + * The proc filesystem: function to read and entry + */ + +void sculld_proc_offset(char *buf, char **start, off_t *offset, int *len) +{ + if (*offset == 0) + return; + if (*offset >= *len) { + /* Not there yet */ + *offset -= *len; + *len = 0; + } else { + /* We're into the interesting stuff now */ + *start = buf + *offset; + *offset = 0; + } +} + +/* FIXME: Do we need this here?? It be ugly */ +int sculld_read_procmem(char *buf, char **start, off_t offset, + int count, int *eof, void *data) +{ + int i, j, order, qset, len = 0; + int limit = count - 80; /* Don't print more than this */ + struct sculld_dev *d; + + *start = buf; + for(i = 0; i < sculld_devs; i++) { + d = &sculld_devices[i]; + if (down_interruptible (&d->sem)) + return -ERESTARTSYS; + qset = d->qset; /* retrieve the features of each device */ + order = d->order; + len += sprintf(buf+len,"\nDevice %i: qset %i, order %i, sz %li\n", + i, qset, order, (long)(d->size)); + for (; d; d = d->next) { /* scan the list */ + len += sprintf(buf+len," item at %p, qset at %p\n",d,d->data); + sculld_proc_offset (buf, start, &offset, &len); + if (len > limit) + goto out; + if (d->data && !d->next) /* dump only the last item - save space */ + for (j = 0; j < qset; j++) { + if (d->data[j]) + len += sprintf(buf+len," % 4i:%8p\n",j,d->data[j]); + sculld_proc_offset (buf, start, &offset, &len); + if (len > limit) + goto out; + } + } + out: + up (&sculld_devices[i].sem); + if (len > limit) + break; + } + *eof = 1; + return len; +} + +#endif /* SCULLD_USE_PROC */ + +/* + * Open and close + */ + +int sculld_open (struct inode *inode, struct file *filp) +{ + struct sculld_dev *dev; /* device information */ + + /* Find the device */ + dev = container_of(inode->i_cdev, struct sculld_dev, cdev); + + /* now trim to 0 the length of the device if open was write-only */ + if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) { + if (down_interruptible (&dev->sem)) + return -ERESTARTSYS; + sculld_trim(dev); /* ignore errors */ + up (&dev->sem); + } + + /* and use filp->private_data to point to the device data */ + filp->private_data = dev; + + return 0; /* success */ +} + +int sculld_release (struct inode *inode, struct file *filp) +{ + return 0; +} + +/* + * Follow the list + */ +struct sculld_dev *sculld_follow(struct sculld_dev *dev, int n) +{ + while (n--) { + if (!dev->next) { + dev->next = kmalloc(sizeof(struct sculld_dev), GFP_KERNEL); + memset(dev->next, 0, sizeof(struct sculld_dev)); + } + dev = dev->next; + continue; + } + return dev; +} + +/* + * Data management: read and write + */ + +ssize_t sculld_read (struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + struct sculld_dev *dev = filp->private_data; /* the first listitem */ + struct sculld_dev *dptr; + int quantum = PAGE_SIZE << dev->order; + int qset = dev->qset; + int itemsize = quantum * qset; /* how many bytes in the listitem */ + int item, s_pos, q_pos, rest; + ssize_t retval = 0; + + if (down_interruptible (&dev->sem)) + return -ERESTARTSYS; + if (*f_pos > dev->size) + goto nothing; + if (*f_pos + count > dev->size) + count = dev->size - *f_pos; + /* find listitem, qset index, and offset in the quantum */ + item = ((long) *f_pos) / itemsize; + rest = ((long) *f_pos) % itemsize; + s_pos = rest / quantum; q_pos = rest % quantum; + + /* follow the list up to the right position (defined elsewhere) */ + dptr = sculld_follow(dev, item); + + if (!dptr->data) + goto nothing; /* don't fill holes */ + if (!dptr->data[s_pos]) + goto nothing; + if (count > quantum - q_pos) + count = quantum - q_pos; /* read only up to the end of this quantum */ + + if (copy_to_user (buf, dptr->data[s_pos]+q_pos, count)) { + retval = -EFAULT; + goto nothing; + } + up (&dev->sem); + + *f_pos += count; + return count; + + nothing: + up (&dev->sem); + return retval; +} + + + +ssize_t sculld_write (struct file *filp, const char __user *buf, size_t count, + loff_t *f_pos) +{ + struct sculld_dev *dev = filp->private_data; + struct sculld_dev *dptr; + int quantum = PAGE_SIZE << dev->order; + int qset = dev->qset; + int itemsize = quantum * qset; + int item, s_pos, q_pos, rest; + ssize_t retval = -ENOMEM; /* our most likely error */ + + if (down_interruptible (&dev->sem)) + return -ERESTARTSYS; + + /* find listitem, qset index and offset in the quantum */ + item = ((long) *f_pos) / itemsize; + rest = ((long) *f_pos) % itemsize; + s_pos = rest / quantum; q_pos = rest % quantum; + + /* follow the list up to the right position */ + dptr = sculld_follow(dev, item); + if (!dptr->data) { + dptr->data = kmalloc(qset * sizeof(void *), GFP_KERNEL); + if (!dptr->data) + goto nomem; + memset(dptr->data, 0, qset * sizeof(char *)); + } + /* Here's the allocation of a single quantum */ + if (!dptr->data[s_pos]) { + dptr->data[s_pos] = + (void *)__get_free_pages(GFP_KERNEL, dptr->order); + if (!dptr->data[s_pos]) + goto nomem; + memset(dptr->data[s_pos], 0, PAGE_SIZE << dptr->order); + } + if (count > quantum - q_pos) + count = quantum - q_pos; /* write only up to the end of this quantum */ + if (copy_from_user (dptr->data[s_pos]+q_pos, buf, count)) { + retval = -EFAULT; + goto nomem; + } + *f_pos += count; + + /* update the size */ + if (dev->size < *f_pos) + dev->size = *f_pos; + up (&dev->sem); + return count; + + nomem: + up (&dev->sem); + return retval; +} + +/* + * The ioctl() implementation + */ + +int sculld_ioctl (struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + + int err = 0, ret = 0, tmp; + + /* don't even decode wrong cmds: better returning ENOTTY than EFAULT */ + if (_IOC_TYPE(cmd) != SCULLD_IOC_MAGIC) return -ENOTTY; + if (_IOC_NR(cmd) > SCULLD_IOC_MAXNR) return -ENOTTY; + + /* + * the type is a bitmask, and VERIFY_WRITE catches R/W + * transfers. Note that the type is user-oriented, while + * verify_area is kernel-oriented, so the concept of "read" and + * "write" is reversed + */ + if (_IOC_DIR(cmd) & _IOC_READ) + err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); + else if (_IOC_DIR(cmd) & _IOC_WRITE) + err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); + if (err) + return -EFAULT; + + switch(cmd) { + + case SCULLD_IOCRESET: + sculld_qset = SCULLD_QSET; + sculld_order = SCULLD_ORDER; + break; + + case SCULLD_IOCSORDER: /* Set: arg points to the value */ + ret = __get_user(sculld_order, (int __user *) arg); + break; + + case SCULLD_IOCTORDER: /* Tell: arg is the value */ + sculld_order = arg; + break; + + case SCULLD_IOCGORDER: /* Get: arg is pointer to result */ + ret = __put_user (sculld_order, (int __user *) arg); + break; + + case SCULLD_IOCQORDER: /* Query: return it (it's positive) */ + return sculld_order; + + case SCULLD_IOCXORDER: /* eXchange: use arg as pointer */ + tmp = sculld_order; + ret = __get_user(sculld_order, (int __user *) arg); + if (ret == 0) + ret = __put_user(tmp, (int __user *) arg); + break; + + case SCULLD_IOCHORDER: /* sHift: like Tell + Query */ + tmp = sculld_order; + sculld_order = arg; + return tmp; + + case SCULLD_IOCSQSET: + ret = __get_user(sculld_qset, (int __user *) arg); + break; + + case SCULLD_IOCTQSET: + sculld_qset = arg; + break; + + case SCULLD_IOCGQSET: + ret = __put_user(sculld_qset, (int __user *)arg); + break; + + case SCULLD_IOCQQSET: + return sculld_qset; + + case SCULLD_IOCXQSET: + tmp = sculld_qset; + ret = __get_user(sculld_qset, (int __user *)arg); + if (ret == 0) + ret = __put_user(tmp, (int __user *)arg); + break; + + case SCULLD_IOCHQSET: + tmp = sculld_qset; + sculld_qset = arg; + return tmp; + + default: /* redundant, as cmd was checked against MAXNR */ + return -ENOTTY; + } + + return ret; +} + +/* + * The "extended" operations + */ + +loff_t sculld_llseek (struct file *filp, loff_t off, int whence) +{ + struct sculld_dev *dev = filp->private_data; + long newpos; + + switch(whence) { + case 0: /* SEEK_SET */ + newpos = off; + break; + + case 1: /* SEEK_CUR */ + newpos = filp->f_pos + off; + break; + + case 2: /* SEEK_END */ + newpos = dev->size + off; + break; + + default: /* can't happen */ + return -EINVAL; + } + if (newpos<0) return -EINVAL; + filp->f_pos = newpos; + return newpos; +} + + +/* + * A simple asynchronous I/O implementation. + */ + +struct async_work { + struct kiocb *iocb; + int result; + struct work_struct work; +}; + +/* + * "Complete" an asynchronous operation. + */ +static void sculld_do_deferred_op(void *p) +{ + struct async_work *stuff = (struct async_work *) p; + aio_complete(stuff->iocb, stuff->result, 0); + kfree(stuff); +} + + +static int sculld_defer_op(int write, struct kiocb *iocb, char __user *buf, + size_t count, loff_t pos) +{ + struct async_work *stuff; + int result; + + /* Copy now while we can access the buffer */ + if (write) + result = sculld_write(iocb->ki_filp, buf, count, &pos); + else + result = sculld_read(iocb->ki_filp, buf, count, &pos); + + /* If this is a synchronous IOCB, we return our status now. */ + if (is_sync_kiocb(iocb)) + return result; + + /* Otherwise defer the completion for a few milliseconds. */ + stuff = kmalloc (sizeof (*stuff), GFP_KERNEL); + if (stuff == NULL) + return result; /* No memory, just complete now */ + stuff->iocb = iocb; + stuff->result = result; + INIT_WORK(&stuff->work, sculld_do_deferred_op, stuff); + schedule_delayed_work(&stuff->work, HZ/100); + return -EIOCBQUEUED; +} + + +static ssize_t sculld_aio_read(struct kiocb *iocb, char __user *buf, size_t count, + loff_t pos) +{ + return sculld_defer_op(0, iocb, buf, count, pos); +} + +static ssize_t sculld_aio_write(struct kiocb *iocb, const char __user *buf, + size_t count, loff_t pos) +{ + return sculld_defer_op(1, iocb, (char __user *) buf, count, pos); +} + + + +/* + * Mmap *is* available, but confined in a different file + */ +extern int sculld_mmap(struct file *filp, struct vm_area_struct *vma); + + +/* + * The fops + */ + +struct file_operations sculld_fops = { + .owner = THIS_MODULE, + .llseek = sculld_llseek, + .read = sculld_read, + .write = sculld_write, + .ioctl = sculld_ioctl, + .mmap = sculld_mmap, + .open = sculld_open, + .release = sculld_release, + .aio_read = sculld_aio_read, + .aio_write = sculld_aio_write, +}; + +int sculld_trim(struct sculld_dev *dev) +{ + struct sculld_dev *next, *dptr; + int qset = dev->qset; /* "dev" is not-null */ + int i; + + if (dev->vmas) /* don't trim: there are active mappings */ + return -EBUSY; + + for (dptr = dev; dptr; dptr = next) { /* all the list items */ + if (dptr->data) { + /* This code frees a whole quantum-set */ + for (i = 0; i < qset; i++) + if (dptr->data[i]) + free_pages((unsigned long)(dptr->data[i]), + dptr->order); + + kfree(dptr->data); + dptr->data=NULL; + } + next=dptr->next; + if (dptr != dev) kfree(dptr); /* all of them but the first */ + } + dev->size = 0; + dev->qset = sculld_qset; + dev->order = sculld_order; + dev->next = NULL; + return 0; +} + + +static void sculld_setup_cdev(struct sculld_dev *dev, int index) +{ + int err, devno = MKDEV(sculld_major, index); + + cdev_init(&dev->cdev, &sculld_fops); + dev->cdev.owner = THIS_MODULE; + dev->cdev.ops = &sculld_fops; + err = cdev_add (&dev->cdev, devno, 1); + /* Fail gracefully if need be */ + if (err) + printk(KERN_NOTICE "Error %d adding scull%d", err, index); +} + +static ssize_t sculld_show_dev(struct device *ddev, char *buf) +{ + struct sculld_dev *dev = ddev->driver_data; + + return print_dev_t(buf, dev->cdev.dev); +} + +static DEVICE_ATTR(dev, S_IRUGO, sculld_show_dev, NULL); + +static void sculld_register_dev(struct sculld_dev *dev, int index) +{ + sprintf(dev->devname, "sculld%d", index); + dev->ldev.name = dev->devname; + dev->ldev.driver = &sculld_driver; + dev->ldev.dev.driver_data = dev; + register_ldd_device(&dev->ldev); + device_create_file(&dev->ldev.dev, &dev_attr_dev); +} + + +/* + * Finally, the module stuff + */ + +int sculld_init(void) +{ + int result, i; + dev_t dev = MKDEV(sculld_major, 0); + + /* + * Register your major, and accept a dynamic number. + */ + if (sculld_major) + result = register_chrdev_region(dev, sculld_devs, "sculld"); + else { + result = alloc_chrdev_region(&dev, 0, sculld_devs, "sculld"); + sculld_major = MAJOR(dev); + } + if (result < 0) + return result; + + /* + * Register with the driver core. + */ + register_ldd_driver(&sculld_driver); + + /* + * allocate the devices -- we can't have them static, as the number + * can be specified at load time + */ + sculld_devices = kmalloc(sculld_devs*sizeof (struct sculld_dev), GFP_KERNEL); + if (!sculld_devices) { + result = -ENOMEM; + goto fail_malloc; + } + memset(sculld_devices, 0, sculld_devs*sizeof (struct sculld_dev)); + for (i = 0; i < sculld_devs; i++) { + sculld_devices[i].order = sculld_order; + sculld_devices[i].qset = sculld_qset; + sema_init (&sculld_devices[i].sem, 1); + sculld_setup_cdev(sculld_devices + i, i); + sculld_register_dev(sculld_devices + i, i); + } + + +#ifdef SCULLD_USE_PROC /* only when available */ + create_proc_read_entry("sculldmem", 0, NULL, sculld_read_procmem, NULL); +#endif + return 0; /* succeed */ + + fail_malloc: + unregister_chrdev_region(dev, sculld_devs); + return result; +} + + + +void sculld_cleanup(void) +{ + int i; + +#ifdef SCULLD_USE_PROC + remove_proc_entry("sculldmem", NULL); +#endif + + for (i = 0; i < sculld_devs; i++) { + unregister_ldd_device(&sculld_devices[i].ldev); + cdev_del(&sculld_devices[i].cdev); + sculld_trim(sculld_devices + i); + } + kfree(sculld_devices); + unregister_ldd_driver(&sculld_driver); + unregister_chrdev_region(MKDEV (sculld_major, 0), sculld_devs); +} + + +module_init(sculld_init); +module_exit(sculld_cleanup); diff --git a/sculld/mmap.c b/sculld/mmap.c new file mode 100644 index 0000000..9325997 --- /dev/null +++ b/sculld/mmap.c @@ -0,0 +1,118 @@ +/* -*- C -*- + * mmap.c -- memory mapping for the sculld char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: _mmap.c.in,v 1.13 2004/10/18 18:07:36 corbet Exp $ + */ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/mm.h> /* everything */ +#include <linux/errno.h> /* error codes */ +#include <asm/pgtable.h> + +#include "sculld.h" /* local definitions */ + + +/* + * open and close: just keep track of how many times the device is + * mapped, to avoid releasing it. + */ + +void sculld_vma_open(struct vm_area_struct *vma) +{ + struct sculld_dev *dev = vma->vm_private_data; + + dev->vmas++; +} + +void sculld_vma_close(struct vm_area_struct *vma) +{ + struct sculld_dev *dev = vma->vm_private_data; + + dev->vmas--; +} + +/* + * The nopage method: the core of the file. It retrieves the + * page required from the sculld device and returns it to the + * user. The count for the page must be incremented, because + * it is automatically decremented at page unmap. + * + * For this reason, "order" must be zero. Otherwise, only the first + * page has its count incremented, and the allocating module must + * release it as a whole block. Therefore, it isn't possible to map + * pages from a multipage block: when they are unmapped, their count + * is individually decreased, and would drop to 0. + */ + +struct page *sculld_vma_nopage(struct vm_area_struct *vma, + unsigned long address, int *type) +{ + unsigned long offset; + struct sculld_dev *ptr, *dev = vma->vm_private_data; + struct page *page = NOPAGE_SIGBUS; + void *pageptr = NULL; /* default to "missing" */ + + down(&dev->sem); + offset = (address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT); + if (offset >= dev->size) goto out; /* out of range */ + + /* + * Now retrieve the sculld device from the list,then the page. + * If the device has holes, the process receives a SIGBUS when + * accessing the hole. + */ + offset >>= PAGE_SHIFT; /* offset is a number of pages */ + for (ptr = dev; ptr && offset >= dev->qset;) { + ptr = ptr->next; + offset -= dev->qset; + } + if (ptr && ptr->data) pageptr = ptr->data[offset]; + if (!pageptr) goto out; /* hole or end-of-file */ + + /* got it, now increment the count */ + get_page(page); + if (type) + *type = VM_FAULT_MINOR; + out: + up(&dev->sem); + return page; +} + + + +struct vm_operations_struct sculld_vm_ops = { + .open = sculld_vma_open, + .close = sculld_vma_close, + .nopage = sculld_vma_nopage, +}; + + +int sculld_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct inode *inode = filp->f_dentry->d_inode; + + /* refuse to map if order is not 0 */ + if (sculld_devices[iminor(inode)].order) + return -ENODEV; + + /* don't do anything here: "nopage" will set up page table entries */ + vma->vm_ops = &sculld_vm_ops; + vma->vm_flags |= VM_RESERVED; + vma->vm_private_data = filp->private_data; + sculld_vma_open(vma); + return 0; +} + diff --git a/sculld/sculld.h b/sculld/sculld.h new file mode 100644 index 0000000..8c21f75 --- /dev/null +++ b/sculld/sculld.h @@ -0,0 +1,126 @@ +/* -*- C -*- + * sculld.h -- definitions for the sculld char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + */ + +#include <linux/ioctl.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include "../include/lddbus.h" + +/* + * Macros to help debugging + */ + +#undef PDEBUG /* undef it, just in case */ +#ifdef SCULLD_DEBUG +# ifdef __KERNEL__ + /* This one if debugging is on, and kernel space */ +# define PDEBUG(fmt, args...) printk( KERN_DEBUG "sculld: " fmt, ## args) +# else + /* This one for user space */ +# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) +# endif +#else +# define PDEBUG(fmt, args...) /* not debugging: nothing */ +#endif + +#undef PDEBUGG +#define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */ + +#define SCULLD_MAJOR 0 /* dynamic major by default */ + +#define SCULLD_DEVS 4 /* sculld0 through sculld3 */ + +/* + * The bare device is a variable-length region of memory. + * Use a linked list of indirect blocks. + * + * "sculld_dev->data" points to an array of pointers, each + * pointer refers to a memory page. + * + * The array (quantum-set) is SCULLD_QSET long. + */ +#define SCULLD_ORDER 0 /* one page at a time */ +#define SCULLD_QSET 500 + +struct sculld_dev { + void **data; + struct sculld_dev *next; /* next listitem */ + int vmas; /* active mappings */ + int order; /* the current allocation order */ + int qset; /* the current array size */ + size_t size; /* 32-bit will suffice */ + struct semaphore sem; /* Mutual exclusion */ + struct cdev cdev; + char devname[20]; + struct ldd_device ldev; +}; + +extern struct sculld_dev *sculld_devices; + +extern struct file_operations sculld_fops; + +/* + * The different configurable parameters + */ +extern int sculld_major; /* main.c */ +extern int sculld_devs; +extern int sculld_order; +extern int sculld_qset; + +/* + * Prototypes for shared functions + */ +int sculld_trim(struct sculld_dev *dev); +struct sculld_dev *sculld_follow(struct sculld_dev *dev, int n); + + +#ifdef SCULLD_DEBUG +# define SCULLD_USE_PROC +#endif + +/* + * Ioctl definitions + */ + +/* Use 'K' as magic number */ +#define SCULLD_IOC_MAGIC 'K' + +#define SCULLD_IOCRESET _IO(SCULLD_IOC_MAGIC, 0) + +/* + * S means "Set" through a ptr, + * T means "Tell" directly + * G means "Get" (to a pointed var) + * Q means "Query", response is on the return value + * X means "eXchange": G and S atomically + * H means "sHift": T and Q atomically + */ +#define SCULLD_IOCSORDER _IOW(SCULLD_IOC_MAGIC, 1, int) +#define SCULLD_IOCTORDER _IO(SCULLD_IOC_MAGIC, 2) +#define SCULLD_IOCGORDER _IOR(SCULLD_IOC_MAGIC, 3, int) +#define SCULLD_IOCQORDER _IO(SCULLD_IOC_MAGIC, 4) +#define SCULLD_IOCXORDER _IOWR(SCULLD_IOC_MAGIC, 5, int) +#define SCULLD_IOCHORDER _IO(SCULLD_IOC_MAGIC, 6) +#define SCULLD_IOCSQSET _IOW(SCULLD_IOC_MAGIC, 7, int) +#define SCULLD_IOCTQSET _IO(SCULLD_IOC_MAGIC, 8) +#define SCULLD_IOCGQSET _IOR(SCULLD_IOC_MAGIC, 9, int) +#define SCULLD_IOCQQSET _IO(SCULLD_IOC_MAGIC, 10) +#define SCULLD_IOCXQSET _IOWR(SCULLD_IOC_MAGIC,11, int) +#define SCULLD_IOCHQSET _IO(SCULLD_IOC_MAGIC, 12) + +#define SCULLD_IOC_MAXNR 12 + + + diff --git a/sculld/sculld_load b/sculld/sculld_load new file mode 100644 index 0000000..14f5541 --- /dev/null +++ b/sculld/sculld_load @@ -0,0 +1,30 @@ +#!/bin/sh +module="sculld" +device="sculld" +mode="664" + +# Group: since distributions do it differently, look for wheel or use staff +if grep '^staff:' /etc/group > /dev/null; then + group="staff" +else + group="wheel" +fi + +# remove stale nodes +rm -f /dev/${device}? + +# invoke insmod with all arguments we got +# and use a pathname, as newer modutils don't look in . by default +/sbin/insmod -f ./$module.ko $* || exit 1 + +major=`cat /proc/devices | awk "\\$2==\"$module\" {print \\$1}"` + +mknod /dev/${device}0 c $major 0 +mknod /dev/${device}1 c $major 1 +mknod /dev/${device}2 c $major 2 +mknod /dev/${device}3 c $major 3 +ln -sf ${device}0 /dev/${device} + +# give appropriate group/permissions +chgrp $group /dev/${device}[0-3] +chmod $mode /dev/${device}[0-3] diff --git a/sculld/sculld_unload b/sculld/sculld_unload new file mode 100644 index 0000000..edebbf9 --- /dev/null +++ b/sculld/sculld_unload @@ -0,0 +1,11 @@ +#!/bin/sh +module="sculld" +device="sculld" + +# invoke rmmod with all arguments we got +/sbin/rmmod $module $* || exit 1 + +# remove nodes +rm -f /dev/${device}[0-3] /dev/${device} + +exit 0 diff --git a/scullp/Makefile b/scullp/Makefile new file mode 100644 index 0000000..2a74203 --- /dev/null +++ b/scullp/Makefile @@ -0,0 +1,46 @@ + +# Comment/uncomment the following line to enable/disable debugging +#DEBUG = y + + +ifeq ($(DEBUG),y) + DEBFLAGS = -O -g -DSCULLP_DEBUG # "-O" is needed to expand inlines +else + DEBFLAGS = -O2 +endif + +CFLAGS += $(DEBFLAGS) -I$(LDDINC) + +TARGET = scullp + +ifneq ($(KERNELRELEASE),) + +scullp-objs := main.o mmap.o + +obj-m := scullp.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +modules: + $(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD) modules + +endif + + +install: + install -d $(INSTALLDIR) + install -c $(TARGET).o $(INSTALLDIR) + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/scullp/main.c b/scullp/main.c new file mode 100644 index 0000000..c48dd79 --- /dev/null +++ b/scullp/main.c @@ -0,0 +1,598 @@ +/* -*- C -*- + * main.c -- the bare scullp char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: _main.c.in,v 1.21 2004/10/14 20:11:39 corbet Exp $ + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/slab.h> /* kmalloc() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/types.h> /* size_t */ +#include <linux/proc_fs.h> +#include <linux/fcntl.h> /* O_ACCMODE */ +#include <linux/aio.h> +#include <asm/uaccess.h> +#include "scullp.h" /* local definitions */ + + +int scullp_major = SCULLP_MAJOR; +int scullp_devs = SCULLP_DEVS; /* number of bare scullp devices */ +int scullp_qset = SCULLP_QSET; +int scullp_order = SCULLP_ORDER; + +module_param(scullp_major, int, 0); +module_param(scullp_devs, int, 0); +module_param(scullp_qset, int, 0); +module_param(scullp_order, int, 0); +MODULE_AUTHOR("Alessandro Rubini"); +MODULE_LICENSE("Dual BSD/GPL"); + +struct scullp_dev *scullp_devices; /* allocated in scullp_init */ + +int scullp_trim(struct scullp_dev *dev); +void scullp_cleanup(void); + + + + + + +#ifdef SCULLP_USE_PROC /* don't waste space if unused */ +/* + * The proc filesystem: function to read and entry + */ + +void scullp_proc_offset(char *buf, char **start, off_t *offset, int *len) +{ + if (*offset == 0) + return; + if (*offset >= *len) { + /* Not there yet */ + *offset -= *len; + *len = 0; + } else { + /* We're into the interesting stuff now */ + *start = buf + *offset; + *offset = 0; + } +} + +/* FIXME: Do we need this here?? It be ugly */ +int scullp_read_procmem(char *buf, char **start, off_t offset, + int count, int *eof, void *data) +{ + int i, j, order, qset, len = 0; + int limit = count - 80; /* Don't print more than this */ + struct scullp_dev *d; + + *start = buf; + for(i = 0; i < scullp_devs; i++) { + d = &scullp_devices[i]; + if (down_interruptible (&d->sem)) + return -ERESTARTSYS; + qset = d->qset; /* retrieve the features of each device */ + order = d->order; + len += sprintf(buf+len,"\nDevice %i: qset %i, order %i, sz %li\n", + i, qset, order, (long)(d->size)); + for (; d; d = d->next) { /* scan the list */ + len += sprintf(buf+len," item at %p, qset at %p\n",d,d->data); + scullp_proc_offset (buf, start, &offset, &len); + if (len > limit) + goto out; + if (d->data && !d->next) /* dump only the last item - save space */ + for (j = 0; j < qset; j++) { + if (d->data[j]) + len += sprintf(buf+len," % 4i:%8p\n",j,d->data[j]); + scullp_proc_offset (buf, start, &offset, &len); + if (len > limit) + goto out; + } + } + out: + up (&scullp_devices[i].sem); + if (len > limit) + break; + } + *eof = 1; + return len; +} + +#endif /* SCULLP_USE_PROC */ + +/* + * Open and close + */ + +int scullp_open (struct inode *inode, struct file *filp) +{ + struct scullp_dev *dev; /* device information */ + + /* Find the device */ + dev = container_of(inode->i_cdev, struct scullp_dev, cdev); + + /* now trim to 0 the length of the device if open was write-only */ + if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) { + if (down_interruptible (&dev->sem)) + return -ERESTARTSYS; + scullp_trim(dev); /* ignore errors */ + up (&dev->sem); + } + + /* and use filp->private_data to point to the device data */ + filp->private_data = dev; + + return 0; /* success */ +} + +int scullp_release (struct inode *inode, struct file *filp) +{ + return 0; +} + +/* + * Follow the list + */ +struct scullp_dev *scullp_follow(struct scullp_dev *dev, int n) +{ + while (n--) { + if (!dev->next) { + dev->next = kmalloc(sizeof(struct scullp_dev), GFP_KERNEL); + memset(dev->next, 0, sizeof(struct scullp_dev)); + } + dev = dev->next; + continue; + } + return dev; +} + +/* + * Data management: read and write + */ + +ssize_t scullp_read (struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + struct scullp_dev *dev = filp->private_data; /* the first listitem */ + struct scullp_dev *dptr; + int quantum = PAGE_SIZE << dev->order; + int qset = dev->qset; + int itemsize = quantum * qset; /* how many bytes in the listitem */ + int item, s_pos, q_pos, rest; + ssize_t retval = 0; + + if (down_interruptible (&dev->sem)) + return -ERESTARTSYS; + if (*f_pos > dev->size) + goto nothing; + if (*f_pos + count > dev->size) + count = dev->size - *f_pos; + /* find listitem, qset index, and offset in the quantum */ + item = ((long) *f_pos) / itemsize; + rest = ((long) *f_pos) % itemsize; + s_pos = rest / quantum; q_pos = rest % quantum; + + /* follow the list up to the right position (defined elsewhere) */ + dptr = scullp_follow(dev, item); + + if (!dptr->data) + goto nothing; /* don't fill holes */ + if (!dptr->data[s_pos]) + goto nothing; + if (count > quantum - q_pos) + count = quantum - q_pos; /* read only up to the end of this quantum */ + + if (copy_to_user (buf, dptr->data[s_pos]+q_pos, count)) { + retval = -EFAULT; + goto nothing; + } + up (&dev->sem); + + *f_pos += count; + return count; + + nothing: + up (&dev->sem); + return retval; +} + + + +ssize_t scullp_write (struct file *filp, const char __user *buf, size_t count, + loff_t *f_pos) +{ + struct scullp_dev *dev = filp->private_data; + struct scullp_dev *dptr; + int quantum = PAGE_SIZE << dev->order; + int qset = dev->qset; + int itemsize = quantum * qset; + int item, s_pos, q_pos, rest; + ssize_t retval = -ENOMEM; /* our most likely error */ + + if (down_interruptible (&dev->sem)) + return -ERESTARTSYS; + + /* find listitem, qset index and offset in the quantum */ + item = ((long) *f_pos) / itemsize; + rest = ((long) *f_pos) % itemsize; + s_pos = rest / quantum; q_pos = rest % quantum; + + /* follow the list up to the right position */ + dptr = scullp_follow(dev, item); + if (!dptr->data) { + dptr->data = kmalloc(qset * sizeof(void *), GFP_KERNEL); + if (!dptr->data) + goto nomem; + memset(dptr->data, 0, qset * sizeof(char *)); + } + /* Here's the allocation of a single quantum */ + if (!dptr->data[s_pos]) { + dptr->data[s_pos] = + (void *)__get_free_pages(GFP_KERNEL, dptr->order); + if (!dptr->data[s_pos]) + goto nomem; + memset(dptr->data[s_pos], 0, PAGE_SIZE << dptr->order); + } + if (count > quantum - q_pos) + count = quantum - q_pos; /* write only up to the end of this quantum */ + if (copy_from_user (dptr->data[s_pos]+q_pos, buf, count)) { + retval = -EFAULT; + goto nomem; + } + *f_pos += count; + + /* update the size */ + if (dev->size < *f_pos) + dev->size = *f_pos; + up (&dev->sem); + return count; + + nomem: + up (&dev->sem); + return retval; +} + +/* + * The ioctl() implementation + */ + +int scullp_ioctl (struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + + int err = 0, ret = 0, tmp; + + /* don't even decode wrong cmds: better returning ENOTTY than EFAULT */ + if (_IOC_TYPE(cmd) != SCULLP_IOC_MAGIC) return -ENOTTY; + if (_IOC_NR(cmd) > SCULLP_IOC_MAXNR) return -ENOTTY; + + /* + * the type is a bitmask, and VERIFY_WRITE catches R/W + * transfers. Note that the type is user-oriented, while + * verify_area is kernel-oriented, so the concept of "read" and + * "write" is reversed + */ + if (_IOC_DIR(cmd) & _IOC_READ) + err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); + else if (_IOC_DIR(cmd) & _IOC_WRITE) + err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); + if (err) + return -EFAULT; + + switch(cmd) { + + case SCULLP_IOCRESET: + scullp_qset = SCULLP_QSET; + scullp_order = SCULLP_ORDER; + break; + + case SCULLP_IOCSORDER: /* Set: arg points to the value */ + ret = __get_user(scullp_order, (int __user *) arg); + break; + + case SCULLP_IOCTORDER: /* Tell: arg is the value */ + scullp_order = arg; + break; + + case SCULLP_IOCGORDER: /* Get: arg is pointer to result */ + ret = __put_user (scullp_order, (int __user *) arg); + break; + + case SCULLP_IOCQORDER: /* Query: return it (it's positive) */ + return scullp_order; + + case SCULLP_IOCXORDER: /* eXchange: use arg as pointer */ + tmp = scullp_order; + ret = __get_user(scullp_order, (int __user *) arg); + if (ret == 0) + ret = __put_user(tmp, (int __user *) arg); + break; + + case SCULLP_IOCHORDER: /* sHift: like Tell + Query */ + tmp = scullp_order; + scullp_order = arg; + return tmp; + + case SCULLP_IOCSQSET: + ret = __get_user(scullp_qset, (int __user *) arg); + break; + + case SCULLP_IOCTQSET: + scullp_qset = arg; + break; + + case SCULLP_IOCGQSET: + ret = __put_user(scullp_qset, (int __user *)arg); + break; + + case SCULLP_IOCQQSET: + return scullp_qset; + + case SCULLP_IOCXQSET: + tmp = scullp_qset; + ret = __get_user(scullp_qset, (int __user *)arg); + if (ret == 0) + ret = __put_user(tmp, (int __user *)arg); + break; + + case SCULLP_IOCHQSET: + tmp = scullp_qset; + scullp_qset = arg; + return tmp; + + default: /* redundant, as cmd was checked against MAXNR */ + return -ENOTTY; + } + + return ret; +} + +/* + * The "extended" operations + */ + +loff_t scullp_llseek (struct file *filp, loff_t off, int whence) +{ + struct scullp_dev *dev = filp->private_data; + long newpos; + + switch(whence) { + case 0: /* SEEK_SET */ + newpos = off; + break; + + case 1: /* SEEK_CUR */ + newpos = filp->f_pos + off; + break; + + case 2: /* SEEK_END */ + newpos = dev->size + off; + break; + + default: /* can't happen */ + return -EINVAL; + } + if (newpos<0) return -EINVAL; + filp->f_pos = newpos; + return newpos; +} + + +/* + * A simple asynchronous I/O implementation. + */ + +struct async_work { + struct kiocb *iocb; + int result; + struct work_struct work; +}; + +/* + * "Complete" an asynchronous operation. + */ +static void scullp_do_deferred_op(void *p) +{ + struct async_work *stuff = (struct async_work *) p; + aio_complete(stuff->iocb, stuff->result, 0); + kfree(stuff); +} + + +static int scullp_defer_op(int write, struct kiocb *iocb, char __user *buf, + size_t count, loff_t pos) +{ + struct async_work *stuff; + int result; + + /* Copy now while we can access the buffer */ + if (write) + result = scullp_write(iocb->ki_filp, buf, count, &pos); + else + result = scullp_read(iocb->ki_filp, buf, count, &pos); + + /* If this is a synchronous IOCB, we return our status now. */ + if (is_sync_kiocb(iocb)) + return result; + + /* Otherwise defer the completion for a few milliseconds. */ + stuff = kmalloc (sizeof (*stuff), GFP_KERNEL); + if (stuff == NULL) + return result; /* No memory, just complete now */ + stuff->iocb = iocb; + stuff->result = result; + INIT_WORK(&stuff->work, scullp_do_deferred_op, stuff); + schedule_delayed_work(&stuff->work, HZ/100); + return -EIOCBQUEUED; +} + + +static ssize_t scullp_aio_read(struct kiocb *iocb, char __user *buf, size_t count, + loff_t pos) +{ + return scullp_defer_op(0, iocb, buf, count, pos); +} + +static ssize_t scullp_aio_write(struct kiocb *iocb, const char __user *buf, + size_t count, loff_t pos) +{ + return scullp_defer_op(1, iocb, (char __user *) buf, count, pos); +} + + + +/* + * Mmap *is* available, but confined in a different file + */ +extern int scullp_mmap(struct file *filp, struct vm_area_struct *vma); + + +/* + * The fops + */ + +struct file_operations scullp_fops = { + .owner = THIS_MODULE, + .llseek = scullp_llseek, + .read = scullp_read, + .write = scullp_write, + .ioctl = scullp_ioctl, + .mmap = scullp_mmap, + .open = scullp_open, + .release = scullp_release, + .aio_read = scullp_aio_read, + .aio_write = scullp_aio_write, +}; + +int scullp_trim(struct scullp_dev *dev) +{ + struct scullp_dev *next, *dptr; + int qset = dev->qset; /* "dev" is not-null */ + int i; + + if (dev->vmas) /* don't trim: there are active mappings */ + return -EBUSY; + + for (dptr = dev; dptr; dptr = next) { /* all the list items */ + if (dptr->data) { + /* This code frees a whole quantum-set */ + for (i = 0; i < qset; i++) + if (dptr->data[i]) + free_pages((unsigned long)(dptr->data[i]), + dptr->order); + + kfree(dptr->data); + dptr->data=NULL; + } + next=dptr->next; + if (dptr != dev) kfree(dptr); /* all of them but the first */ + } + dev->size = 0; + dev->qset = scullp_qset; + dev->order = scullp_order; + dev->next = NULL; + return 0; +} + + +static void scullp_setup_cdev(struct scullp_dev *dev, int index) +{ + int err, devno = MKDEV(scullp_major, index); + + cdev_init(&dev->cdev, &scullp_fops); + dev->cdev.owner = THIS_MODULE; + dev->cdev.ops = &scullp_fops; + err = cdev_add (&dev->cdev, devno, 1); + /* Fail gracefully if need be */ + if (err) + printk(KERN_NOTICE "Error %d adding scull%d", err, index); +} + + + +/* + * Finally, the module stuff + */ + +int scullp_init(void) +{ + int result, i; + dev_t dev = MKDEV(scullp_major, 0); + + /* + * Register your major, and accept a dynamic number. + */ + if (scullp_major) + result = register_chrdev_region(dev, scullp_devs, "scullp"); + else { + result = alloc_chrdev_region(&dev, 0, scullp_devs, "scullp"); + scullp_major = MAJOR(dev); + } + if (result < 0) + return result; + + + /* + * allocate the devices -- we can't have them static, as the number + * can be specified at load time + */ + scullp_devices = kmalloc(scullp_devs*sizeof (struct scullp_dev), GFP_KERNEL); + if (!scullp_devices) { + result = -ENOMEM; + goto fail_malloc; + } + memset(scullp_devices, 0, scullp_devs*sizeof (struct scullp_dev)); + for (i = 0; i < scullp_devs; i++) { + scullp_devices[i].order = scullp_order; + scullp_devices[i].qset = scullp_qset; + sema_init (&scullp_devices[i].sem, 1); + scullp_setup_cdev(scullp_devices + i, i); + } + + +#ifdef SCULLP_USE_PROC /* only when available */ + create_proc_read_entry("scullpmem", 0, NULL, scullp_read_procmem, NULL); +#endif + return 0; /* succeed */ + + fail_malloc: + unregister_chrdev_region(dev, scullp_devs); + return result; +} + + + +void scullp_cleanup(void) +{ + int i; + +#ifdef SCULLP_USE_PROC + remove_proc_entry("scullpmem", NULL); +#endif + + for (i = 0; i < scullp_devs; i++) { + cdev_del(&scullp_devices[i].cdev); + scullp_trim(scullp_devices + i); + } + kfree(scullp_devices); + unregister_chrdev_region(MKDEV (scullp_major, 0), scullp_devs); +} + + +module_init(scullp_init); +module_exit(scullp_cleanup); diff --git a/scullp/mmap.c b/scullp/mmap.c new file mode 100644 index 0000000..f5166db --- /dev/null +++ b/scullp/mmap.c @@ -0,0 +1,119 @@ +/* -*- C -*- + * mmap.c -- memory mapping for the scullp char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: _mmap.c.in,v 1.13 2004/10/18 18:07:36 corbet Exp $ + */ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/mm.h> /* everything */ +#include <linux/errno.h> /* error codes */ +#include <asm/pgtable.h> + +#include "scullp.h" /* local definitions */ + + +/* + * open and close: just keep track of how many times the device is + * mapped, to avoid releasing it. + */ + +void scullp_vma_open(struct vm_area_struct *vma) +{ + struct scullp_dev *dev = vma->vm_private_data; + + dev->vmas++; +} + +void scullp_vma_close(struct vm_area_struct *vma) +{ + struct scullp_dev *dev = vma->vm_private_data; + + dev->vmas--; +} + +/* + * The nopage method: the core of the file. It retrieves the + * page required from the scullp device and returns it to the + * user. The count for the page must be incremented, because + * it is automatically decremented at page unmap. + * + * For this reason, "order" must be zero. Otherwise, only the first + * page has its count incremented, and the allocating module must + * release it as a whole block. Therefore, it isn't possible to map + * pages from a multipage block: when they are unmapped, their count + * is individually decreased, and would drop to 0. + */ + +struct page *scullp_vma_nopage(struct vm_area_struct *vma, + unsigned long address, int *type) +{ + unsigned long offset; + struct scullp_dev *ptr, *dev = vma->vm_private_data; + struct page *page = NOPAGE_SIGBUS; + void *pageptr = NULL; /* default to "missing" */ + + down(&dev->sem); + offset = (address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT); + if (offset >= dev->size) goto out; /* out of range */ + + /* + * Now retrieve the scullp device from the list,then the page. + * If the device has holes, the process receives a SIGBUS when + * accessing the hole. + */ + offset >>= PAGE_SHIFT; /* offset is a number of pages */ + for (ptr = dev; ptr && offset >= dev->qset;) { + ptr = ptr->next; + offset -= dev->qset; + } + if (ptr && ptr->data) pageptr = ptr->data[offset]; + if (!pageptr) goto out; /* hole or end-of-file */ + page = virt_to_page(pageptr); + + /* got it, now increment the count */ + get_page(page); + if (type) + *type = VM_FAULT_MINOR; + out: + up(&dev->sem); + return page; +} + + + +struct vm_operations_struct scullp_vm_ops = { + .open = scullp_vma_open, + .close = scullp_vma_close, + .nopage = scullp_vma_nopage, +}; + + +int scullp_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct inode *inode = filp->f_dentry->d_inode; + + /* refuse to map if order is not 0 */ + if (scullp_devices[iminor(inode)].order) + return -ENODEV; + + /* don't do anything here: "nopage" will set up page table entries */ + vma->vm_ops = &scullp_vm_ops; + vma->vm_flags |= VM_RESERVED; + vma->vm_private_data = filp->private_data; + scullp_vma_open(vma); + return 0; +} + diff --git a/scullp/scullp.h b/scullp/scullp.h new file mode 100644 index 0000000..949bd25 --- /dev/null +++ b/scullp/scullp.h @@ -0,0 +1,122 @@ +/* -*- C -*- + * scullp.h -- definitions for the scullp char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + */ + +#include <linux/ioctl.h> +#include <linux/cdev.h> + +/* + * Macros to help debugging + */ + +#undef PDEBUG /* undef it, just in case */ +#ifdef SCULLP_DEBUG +# ifdef __KERNEL__ + /* This one if debugging is on, and kernel space */ +# define PDEBUG(fmt, args...) printk( KERN_DEBUG "scullp: " fmt, ## args) +# else + /* This one for user space */ +# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) +# endif +#else +# define PDEBUG(fmt, args...) /* not debugging: nothing */ +#endif + +#undef PDEBUGG +#define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */ + +#define SCULLP_MAJOR 0 /* dynamic major by default */ + +#define SCULLP_DEVS 4 /* scullp0 through scullp3 */ + +/* + * The bare device is a variable-length region of memory. + * Use a linked list of indirect blocks. + * + * "scullp_dev->data" points to an array of pointers, each + * pointer refers to a memory page. + * + * The array (quantum-set) is SCULLP_QSET long. + */ +#define SCULLP_ORDER 0 /* one page at a time */ +#define SCULLP_QSET 500 + +struct scullp_dev { + void **data; + struct scullp_dev *next; /* next listitem */ + int vmas; /* active mappings */ + int order; /* the current allocation order */ + int qset; /* the current array size */ + size_t size; /* 32-bit will suffice */ + struct semaphore sem; /* Mutual exclusion */ + struct cdev cdev; +}; + +extern struct scullp_dev *scullp_devices; + +extern struct file_operations scullp_fops; + +/* + * The different configurable parameters + */ +extern int scullp_major; /* main.c */ +extern int scullp_devs; +extern int scullp_order; +extern int scullp_qset; + +/* + * Prototypes for shared functions + */ +int scullp_trim(struct scullp_dev *dev); +struct scullp_dev *scullp_follow(struct scullp_dev *dev, int n); + + +#ifdef SCULLP_DEBUG +# define SCULLP_USE_PROC +#endif + +/* + * Ioctl definitions + */ + +/* Use 'K' as magic number */ +#define SCULLP_IOC_MAGIC 'K' + +#define SCULLP_IOCRESET _IO(SCULLP_IOC_MAGIC, 0) + +/* + * S means "Set" through a ptr, + * T means "Tell" directly + * G means "Get" (to a pointed var) + * Q means "Query", response is on the return value + * X means "eXchange": G and S atomically + * H means "sHift": T and Q atomically + */ +#define SCULLP_IOCSORDER _IOW(SCULLP_IOC_MAGIC, 1, int) +#define SCULLP_IOCTORDER _IO(SCULLP_IOC_MAGIC, 2) +#define SCULLP_IOCGORDER _IOR(SCULLP_IOC_MAGIC, 3, int) +#define SCULLP_IOCQORDER _IO(SCULLP_IOC_MAGIC, 4) +#define SCULLP_IOCXORDER _IOWR(SCULLP_IOC_MAGIC, 5, int) +#define SCULLP_IOCHORDER _IO(SCULLP_IOC_MAGIC, 6) +#define SCULLP_IOCSQSET _IOW(SCULLP_IOC_MAGIC, 7, int) +#define SCULLP_IOCTQSET _IO(SCULLP_IOC_MAGIC, 8) +#define SCULLP_IOCGQSET _IOR(SCULLP_IOC_MAGIC, 9, int) +#define SCULLP_IOCQQSET _IO(SCULLP_IOC_MAGIC, 10) +#define SCULLP_IOCXQSET _IOWR(SCULLP_IOC_MAGIC,11, int) +#define SCULLP_IOCHQSET _IO(SCULLP_IOC_MAGIC, 12) + +#define SCULLP_IOC_MAXNR 12 + + + diff --git a/scullp/scullp_load b/scullp/scullp_load new file mode 100644 index 0000000..d7dc1ef --- /dev/null +++ b/scullp/scullp_load @@ -0,0 +1,30 @@ +#!/bin/sh +module="scullp" +device="scullp" +mode="664" + +# Group: since distributions do it differently, look for wheel or use staff +if grep '^staff:' /etc/group > /dev/null; then + group="staff" +else + group="wheel" +fi + +# remove stale nodes +rm -f /dev/${device}? + +# invoke insmod with all arguments we got +# and use a pathname, as newer modutils don't look in . by default +/sbin/insmod -f ./$module.ko $* || exit 1 + +major=`cat /proc/devices | awk "\\$2==\"$module\" {print \\$1}"` + +mknod /dev/${device}0 c $major 0 +mknod /dev/${device}1 c $major 1 +mknod /dev/${device}2 c $major 2 +mknod /dev/${device}3 c $major 3 +ln -sf ${device}0 /dev/${device} + +# give appropriate group/permissions +chgrp $group /dev/${device}[0-3] +chmod $mode /dev/${device}[0-3] diff --git a/scullp/scullp_unload b/scullp/scullp_unload new file mode 100644 index 0000000..adf5e2c --- /dev/null +++ b/scullp/scullp_unload @@ -0,0 +1,11 @@ +#!/bin/sh +module="scullp" +device="scullp" + +# invoke rmmod with all arguments we got +/sbin/rmmod $module $* || exit 1 + +# remove nodes +rm -f /dev/${device}[0-3] /dev/${device} + +exit 0 diff --git a/scullv/Makefile b/scullv/Makefile new file mode 100644 index 0000000..6dea81f --- /dev/null +++ b/scullv/Makefile @@ -0,0 +1,46 @@ + +# Comment/uncomment the following line to enable/disable debugging +#DEBUG = y + + +ifeq ($(DEBUG),y) + DEBFLAGS = -O -g -DSCULLV_DEBUG # "-O" is needed to expand inlines +else + DEBFLAGS = -O2 +endif + +CFLAGS += $(DEBFLAGS) -I$(LDDINC) + +TARGET = scullv + +ifneq ($(KERNELRELEASE),) + +scullv-objs := main.o mmap.o + +obj-m := scullv.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +modules: + $(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD) modules + +endif + + +install: + install -d $(INSTALLDIR) + install -c $(TARGET).o $(INSTALLDIR) + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/scullv/main.c b/scullv/main.c new file mode 100644 index 0000000..30b57d8 --- /dev/null +++ b/scullv/main.c @@ -0,0 +1,597 @@ +/* -*- C -*- + * main.c -- the bare scullv char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: _main.c.in,v 1.21 2004/10/14 20:11:39 corbet Exp $ + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/slab.h> /* kmalloc() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/types.h> /* size_t */ +#include <linux/proc_fs.h> +#include <linux/fcntl.h> /* O_ACCMODE */ +#include <linux/aio.h> +#include <asm/uaccess.h> +#include <linux/vmalloc.h> +#include "scullv.h" /* local definitions */ + + +int scullv_major = SCULLV_MAJOR; +int scullv_devs = SCULLV_DEVS; /* number of bare scullv devices */ +int scullv_qset = SCULLV_QSET; +int scullv_order = SCULLV_ORDER; + +module_param(scullv_major, int, 0); +module_param(scullv_devs, int, 0); +module_param(scullv_qset, int, 0); +module_param(scullv_order, int, 0); +MODULE_AUTHOR("Alessandro Rubini"); +MODULE_LICENSE("Dual BSD/GPL"); + +struct scullv_dev *scullv_devices; /* allocated in scullv_init */ + +int scullv_trim(struct scullv_dev *dev); +void scullv_cleanup(void); + + + + + + +#ifdef SCULLV_USE_PROC /* don't waste space if unused */ +/* + * The proc filesystem: function to read and entry + */ + +void scullv_proc_offset(char *buf, char **start, off_t *offset, int *len) +{ + if (*offset == 0) + return; + if (*offset >= *len) { + /* Not there yet */ + *offset -= *len; + *len = 0; + } else { + /* We're into the interesting stuff now */ + *start = buf + *offset; + *offset = 0; + } +} + +/* FIXME: Do we need this here?? It be ugly */ +int scullv_read_procmem(char *buf, char **start, off_t offset, + int count, int *eof, void *data) +{ + int i, j, order, qset, len = 0; + int limit = count - 80; /* Don't print more than this */ + struct scullv_dev *d; + + *start = buf; + for(i = 0; i < scullv_devs; i++) { + d = &scullv_devices[i]; + if (down_interruptible (&d->sem)) + return -ERESTARTSYS; + qset = d->qset; /* retrieve the features of each device */ + order = d->order; + len += sprintf(buf+len,"\nDevice %i: qset %i, order %i, sz %li\n", + i, qset, order, (long)(d->size)); + for (; d; d = d->next) { /* scan the list */ + len += sprintf(buf+len," item at %p, qset at %p\n",d,d->data); + scullv_proc_offset (buf, start, &offset, &len); + if (len > limit) + goto out; + if (d->data && !d->next) /* dump only the last item - save space */ + for (j = 0; j < qset; j++) { + if (d->data[j]) + len += sprintf(buf+len," % 4i:%8p\n",j,d->data[j]); + scullv_proc_offset (buf, start, &offset, &len); + if (len > limit) + goto out; + } + } + out: + up (&scullv_devices[i].sem); + if (len > limit) + break; + } + *eof = 1; + return len; +} + +#endif /* SCULLV_USE_PROC */ + +/* + * Open and close + */ + +int scullv_open (struct inode *inode, struct file *filp) +{ + struct scullv_dev *dev; /* device information */ + + /* Find the device */ + dev = container_of(inode->i_cdev, struct scullv_dev, cdev); + + /* now trim to 0 the length of the device if open was write-only */ + if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) { + if (down_interruptible (&dev->sem)) + return -ERESTARTSYS; + scullv_trim(dev); /* ignore errors */ + up (&dev->sem); + } + + /* and use filp->private_data to point to the device data */ + filp->private_data = dev; + + return 0; /* success */ +} + +int scullv_release (struct inode *inode, struct file *filp) +{ + return 0; +} + +/* + * Follow the list + */ +struct scullv_dev *scullv_follow(struct scullv_dev *dev, int n) +{ + while (n--) { + if (!dev->next) { + dev->next = kmalloc(sizeof(struct scullv_dev), GFP_KERNEL); + memset(dev->next, 0, sizeof(struct scullv_dev)); + } + dev = dev->next; + continue; + } + return dev; +} + +/* + * Data management: read and write + */ + +ssize_t scullv_read (struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + struct scullv_dev *dev = filp->private_data; /* the first listitem */ + struct scullv_dev *dptr; + int quantum = PAGE_SIZE << dev->order; + int qset = dev->qset; + int itemsize = quantum * qset; /* how many bytes in the listitem */ + int item, s_pos, q_pos, rest; + ssize_t retval = 0; + + if (down_interruptible (&dev->sem)) + return -ERESTARTSYS; + if (*f_pos > dev->size) + goto nothing; + if (*f_pos + count > dev->size) + count = dev->size - *f_pos; + /* find listitem, qset index, and offset in the quantum */ + item = ((long) *f_pos) / itemsize; + rest = ((long) *f_pos) % itemsize; + s_pos = rest / quantum; q_pos = rest % quantum; + + /* follow the list up to the right position (defined elsewhere) */ + dptr = scullv_follow(dev, item); + + if (!dptr->data) + goto nothing; /* don't fill holes */ + if (!dptr->data[s_pos]) + goto nothing; + if (count > quantum - q_pos) + count = quantum - q_pos; /* read only up to the end of this quantum */ + + if (copy_to_user (buf, dptr->data[s_pos]+q_pos, count)) { + retval = -EFAULT; + goto nothing; + } + up (&dev->sem); + + *f_pos += count; + return count; + + nothing: + up (&dev->sem); + return retval; +} + + + +ssize_t scullv_write (struct file *filp, const char __user *buf, size_t count, + loff_t *f_pos) +{ + struct scullv_dev *dev = filp->private_data; + struct scullv_dev *dptr; + int quantum = PAGE_SIZE << dev->order; + int qset = dev->qset; + int itemsize = quantum * qset; + int item, s_pos, q_pos, rest; + ssize_t retval = -ENOMEM; /* our most likely error */ + + if (down_interruptible (&dev->sem)) + return -ERESTARTSYS; + + /* find listitem, qset index and offset in the quantum */ + item = ((long) *f_pos) / itemsize; + rest = ((long) *f_pos) % itemsize; + s_pos = rest / quantum; q_pos = rest % quantum; + + /* follow the list up to the right position */ + dptr = scullv_follow(dev, item); + if (!dptr->data) { + dptr->data = kmalloc(qset * sizeof(void *), GFP_KERNEL); + if (!dptr->data) + goto nomem; + memset(dptr->data, 0, qset * sizeof(char *)); + } + /* Allocate a quantum using virtual addresses */ + if (!dptr->data[s_pos]) { + dptr->data[s_pos] = (void *)vmalloc(PAGE_SIZE << dptr->order); + if (!dptr->data[s_pos]) + goto nomem; + memset(dptr->data[s_pos], 0, PAGE_SIZE << dptr->order); + } + if (count > quantum - q_pos) + count = quantum - q_pos; /* write only up to the end of this quantum */ + if (copy_from_user (dptr->data[s_pos]+q_pos, buf, count)) { + retval = -EFAULT; + goto nomem; + } + *f_pos += count; + + /* update the size */ + if (dev->size < *f_pos) + dev->size = *f_pos; + up (&dev->sem); + return count; + + nomem: + up (&dev->sem); + return retval; +} + +/* + * The ioctl() implementation + */ + +int scullv_ioctl (struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + + int err = 0, ret = 0, tmp; + + /* don't even decode wrong cmds: better returning ENOTTY than EFAULT */ + if (_IOC_TYPE(cmd) != SCULLV_IOC_MAGIC) return -ENOTTY; + if (_IOC_NR(cmd) > SCULLV_IOC_MAXNR) return -ENOTTY; + + /* + * the type is a bitmask, and VERIFY_WRITE catches R/W + * transfers. Note that the type is user-oriented, while + * verify_area is kernel-oriented, so the concept of "read" and + * "write" is reversed + */ + if (_IOC_DIR(cmd) & _IOC_READ) + err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); + else if (_IOC_DIR(cmd) & _IOC_WRITE) + err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); + if (err) + return -EFAULT; + + switch(cmd) { + + case SCULLV_IOCRESET: + scullv_qset = SCULLV_QSET; + scullv_order = SCULLV_ORDER; + break; + + case SCULLV_IOCSORDER: /* Set: arg points to the value */ + ret = __get_user(scullv_order, (int __user *) arg); + break; + + case SCULLV_IOCTORDER: /* Tell: arg is the value */ + scullv_order = arg; + break; + + case SCULLV_IOCGORDER: /* Get: arg is pointer to result */ + ret = __put_user (scullv_order, (int __user *) arg); + break; + + case SCULLV_IOCQORDER: /* Query: return it (it's positive) */ + return scullv_order; + + case SCULLV_IOCXORDER: /* eXchange: use arg as pointer */ + tmp = scullv_order; + ret = __get_user(scullv_order, (int __user *) arg); + if (ret == 0) + ret = __put_user(tmp, (int __user *) arg); + break; + + case SCULLV_IOCHORDER: /* sHift: like Tell + Query */ + tmp = scullv_order; + scullv_order = arg; + return tmp; + + case SCULLV_IOCSQSET: + ret = __get_user(scullv_qset, (int __user *) arg); + break; + + case SCULLV_IOCTQSET: + scullv_qset = arg; + break; + + case SCULLV_IOCGQSET: + ret = __put_user(scullv_qset, (int __user *)arg); + break; + + case SCULLV_IOCQQSET: + return scullv_qset; + + case SCULLV_IOCXQSET: + tmp = scullv_qset; + ret = __get_user(scullv_qset, (int __user *)arg); + if (ret == 0) + ret = __put_user(tmp, (int __user *)arg); + break; + + case SCULLV_IOCHQSET: + tmp = scullv_qset; + scullv_qset = arg; + return tmp; + + default: /* redundant, as cmd was checked against MAXNR */ + return -ENOTTY; + } + + return ret; +} + +/* + * The "extended" operations + */ + +loff_t scullv_llseek (struct file *filp, loff_t off, int whence) +{ + struct scullv_dev *dev = filp->private_data; + long newpos; + + switch(whence) { + case 0: /* SEEK_SET */ + newpos = off; + break; + + case 1: /* SEEK_CUR */ + newpos = filp->f_pos + off; + break; + + case 2: /* SEEK_END */ + newpos = dev->size + off; + break; + + default: /* can't happen */ + return -EINVAL; + } + if (newpos<0) return -EINVAL; + filp->f_pos = newpos; + return newpos; +} + + +/* + * A simple asynchronous I/O implementation. + */ + +struct async_work { + struct kiocb *iocb; + int result; + struct work_struct work; +}; + +/* + * "Complete" an asynchronous operation. + */ +static void scullv_do_deferred_op(void *p) +{ + struct async_work *stuff = (struct async_work *) p; + aio_complete(stuff->iocb, stuff->result, 0); + kfree(stuff); +} + + +static int scullv_defer_op(int write, struct kiocb *iocb, char __user *buf, + size_t count, loff_t pos) +{ + struct async_work *stuff; + int result; + + /* Copy now while we can access the buffer */ + if (write) + result = scullv_write(iocb->ki_filp, buf, count, &pos); + else + result = scullv_read(iocb->ki_filp, buf, count, &pos); + + /* If this is a synchronous IOCB, we return our status now. */ + if (is_sync_kiocb(iocb)) + return result; + + /* Otherwise defer the completion for a few milliseconds. */ + stuff = kmalloc (sizeof (*stuff), GFP_KERNEL); + if (stuff == NULL) + return result; /* No memory, just complete now */ + stuff->iocb = iocb; + stuff->result = result; + INIT_WORK(&stuff->work, scullv_do_deferred_op, stuff); + schedule_delayed_work(&stuff->work, HZ/100); + return -EIOCBQUEUED; +} + + +static ssize_t scullv_aio_read(struct kiocb *iocb, char __user *buf, size_t count, + loff_t pos) +{ + return scullv_defer_op(0, iocb, buf, count, pos); +} + +static ssize_t scullv_aio_write(struct kiocb *iocb, const char __user *buf, + size_t count, loff_t pos) +{ + return scullv_defer_op(1, iocb, (char __user *) buf, count, pos); +} + + + +/* + * Mmap *is* available, but confined in a different file + */ +extern int scullv_mmap(struct file *filp, struct vm_area_struct *vma); + + +/* + * The fops + */ + +struct file_operations scullv_fops = { + .owner = THIS_MODULE, + .llseek = scullv_llseek, + .read = scullv_read, + .write = scullv_write, + .ioctl = scullv_ioctl, + .mmap = scullv_mmap, + .open = scullv_open, + .release = scullv_release, + .aio_read = scullv_aio_read, + .aio_write = scullv_aio_write, +}; + +int scullv_trim(struct scullv_dev *dev) +{ + struct scullv_dev *next, *dptr; + int qset = dev->qset; /* "dev" is not-null */ + int i; + + if (dev->vmas) /* don't trim: there are active mappings */ + return -EBUSY; + + for (dptr = dev; dptr; dptr = next) { /* all the list items */ + if (dptr->data) { + /* Release the quantum-set */ + for (i = 0; i < qset; i++) + if (dptr->data[i]) + vfree(dptr->data[i]); + + kfree(dptr->data); + dptr->data=NULL; + } + next=dptr->next; + if (dptr != dev) kfree(dptr); /* all of them but the first */ + } + dev->size = 0; + dev->qset = scullv_qset; + dev->order = scullv_order; + dev->next = NULL; + return 0; +} + + +static void scullv_setup_cdev(struct scullv_dev *dev, int index) +{ + int err, devno = MKDEV(scullv_major, index); + + cdev_init(&dev->cdev, &scullv_fops); + dev->cdev.owner = THIS_MODULE; + dev->cdev.ops = &scullv_fops; + err = cdev_add (&dev->cdev, devno, 1); + /* Fail gracefully if need be */ + if (err) + printk(KERN_NOTICE "Error %d adding scull%d", err, index); +} + + + +/* + * Finally, the module stuff + */ + +int scullv_init(void) +{ + int result, i; + dev_t dev = MKDEV(scullv_major, 0); + + /* + * Register your major, and accept a dynamic number. + */ + if (scullv_major) + result = register_chrdev_region(dev, scullv_devs, "scullv"); + else { + result = alloc_chrdev_region(&dev, 0, scullv_devs, "scullv"); + scullv_major = MAJOR(dev); + } + if (result < 0) + return result; + + + /* + * allocate the devices -- we can't have them static, as the number + * can be specified at load time + */ + scullv_devices = kmalloc(scullv_devs*sizeof (struct scullv_dev), GFP_KERNEL); + if (!scullv_devices) { + result = -ENOMEM; + goto fail_malloc; + } + memset(scullv_devices, 0, scullv_devs*sizeof (struct scullv_dev)); + for (i = 0; i < scullv_devs; i++) { + scullv_devices[i].order = scullv_order; + scullv_devices[i].qset = scullv_qset; + sema_init (&scullv_devices[i].sem, 1); + scullv_setup_cdev(scullv_devices + i, i); + } + + +#ifdef SCULLV_USE_PROC /* only when available */ + create_proc_read_entry("scullvmem", 0, NULL, scullv_read_procmem, NULL); +#endif + return 0; /* succeed */ + + fail_malloc: + unregister_chrdev_region(dev, scullv_devs); + return result; +} + + + +void scullv_cleanup(void) +{ + int i; + +#ifdef SCULLV_USE_PROC + remove_proc_entry("scullvmem", NULL); +#endif + + for (i = 0; i < scullv_devs; i++) { + cdev_del(&scullv_devices[i].cdev); + scullv_trim(scullv_devices + i); + } + kfree(scullv_devices); + unregister_chrdev_region(MKDEV (scullv_major, 0), scullv_devs); +} + + +module_init(scullv_init); +module_exit(scullv_cleanup); diff --git a/scullv/mmap.c b/scullv/mmap.c new file mode 100644 index 0000000..0f6656f --- /dev/null +++ b/scullv/mmap.c @@ -0,0 +1,120 @@ +/* -*- C -*- + * mmap.c -- memory mapping for the scullv char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: _mmap.c.in,v 1.13 2004/10/18 18:07:36 corbet Exp $ + */ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/mm.h> /* everything */ +#include <linux/errno.h> /* error codes */ +#include <asm/pgtable.h> + +#include "scullv.h" /* local definitions */ + + +/* + * open and close: just keep track of how many times the device is + * mapped, to avoid releasing it. + */ + +void scullv_vma_open(struct vm_area_struct *vma) +{ + struct scullv_dev *dev = vma->vm_private_data; + + dev->vmas++; +} + +void scullv_vma_close(struct vm_area_struct *vma) +{ + struct scullv_dev *dev = vma->vm_private_data; + + dev->vmas--; +} + +/* + * The nopage method: the core of the file. It retrieves the + * page required from the scullv device and returns it to the + * user. The count for the page must be incremented, because + * it is automatically decremented at page unmap. + * + * For this reason, "order" must be zero. Otherwise, only the first + * page has its count incremented, and the allocating module must + * release it as a whole block. Therefore, it isn't possible to map + * pages from a multipage block: when they are unmapped, their count + * is individually decreased, and would drop to 0. + */ + +struct page *scullv_vma_nopage(struct vm_area_struct *vma, + unsigned long address, int *type) +{ + unsigned long offset; + struct scullv_dev *ptr, *dev = vma->vm_private_data; + struct page *page = NOPAGE_SIGBUS; + void *pageptr = NULL; /* default to "missing" */ + + down(&dev->sem); + offset = (address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT); + if (offset >= dev->size) goto out; /* out of range */ + + /* + * Now retrieve the scullv device from the list,then the page. + * If the device has holes, the process receives a SIGBUS when + * accessing the hole. + */ + offset >>= PAGE_SHIFT; /* offset is a number of pages */ + for (ptr = dev; ptr && offset >= dev->qset;) { + ptr = ptr->next; + offset -= dev->qset; + } + if (ptr && ptr->data) pageptr = ptr->data[offset]; + if (!pageptr) goto out; /* hole or end-of-file */ + + /* + * After scullv lookup, "page" is now the address of the page + * needed by the current process. Since it's a vmalloc address, + * turn it into a struct page. + */ + page = vmalloc_to_page(pageptr); + + /* got it, now increment the count */ + get_page(page); + if (type) + *type = VM_FAULT_MINOR; + out: + up(&dev->sem); + return page; +} + + + +struct vm_operations_struct scullv_vm_ops = { + .open = scullv_vma_open, + .close = scullv_vma_close, + .nopage = scullv_vma_nopage, +}; + + +int scullv_mmap(struct file *filp, struct vm_area_struct *vma) +{ + + /* don't do anything here: "nopage" will set up page table entries */ + vma->vm_ops = &scullv_vm_ops; + vma->vm_flags |= VM_RESERVED; + vma->vm_private_data = filp->private_data; + scullv_vma_open(vma); + return 0; +} + diff --git a/scullv/scullv.h b/scullv/scullv.h new file mode 100644 index 0000000..8ba2595 --- /dev/null +++ b/scullv/scullv.h @@ -0,0 +1,122 @@ +/* -*- C -*- + * scullv.h -- definitions for the scullv char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + */ + +#include <linux/ioctl.h> +#include <linux/cdev.h> + +/* + * Macros to help debugging + */ + +#undef PDEBUG /* undef it, just in case */ +#ifdef SCULLV_DEBUG +# ifdef __KERNEL__ + /* This one if debugging is on, and kernel space */ +# define PDEBUG(fmt, args...) printk( KERN_DEBUG "scullv: " fmt, ## args) +# else + /* This one for user space */ +# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) +# endif +#else +# define PDEBUG(fmt, args...) /* not debugging: nothing */ +#endif + +#undef PDEBUGG +#define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */ + +#define SCULLV_MAJOR 0 /* dynamic major by default */ + +#define SCULLV_DEVS 4 /* scullv0 through scullv3 */ + +/* + * The bare device is a variable-length region of memory. + * Use a linked list of indirect blocks. + * + * "scullv_dev->data" points to an array of pointers, each + * pointer refers to a memory page. + * + * The array (quantum-set) is SCULLV_QSET long. + */ +#define SCULLV_ORDER 4 /* 16 pages at a time */ +#define SCULLV_QSET 500 + +struct scullv_dev { + void **data; + struct scullv_dev *next; /* next listitem */ + int vmas; /* active mappings */ + int order; /* the current allocation order */ + int qset; /* the current array size */ + size_t size; /* 32-bit will suffice */ + struct semaphore sem; /* Mutual exclusion */ + struct cdev cdev; +}; + +extern struct scullv_dev *scullv_devices; + +extern struct file_operations scullv_fops; + +/* + * The different configurable parameters + */ +extern int scullv_major; /* main.c */ +extern int scullv_devs; +extern int scullv_order; +extern int scullv_qset; + +/* + * Prototypes for shared functions + */ +int scullv_trim(struct scullv_dev *dev); +struct scullv_dev *scullv_follow(struct scullv_dev *dev, int n); + + +#ifdef SCULLV_DEBUG +# define SCULLV_USE_PROC +#endif + +/* + * Ioctl definitions + */ + +/* Use 'K' as magic number */ +#define SCULLV_IOC_MAGIC 'K' + +#define SCULLV_IOCRESET _IO(SCULLV_IOC_MAGIC, 0) + +/* + * S means "Set" through a ptr, + * T means "Tell" directly + * G means "Get" (to a pointed var) + * Q means "Query", response is on the return value + * X means "eXchange": G and S atomically + * H means "sHift": T and Q atomically + */ +#define SCULLV_IOCSORDER _IOW(SCULLV_IOC_MAGIC, 1, int) +#define SCULLV_IOCTORDER _IO(SCULLV_IOC_MAGIC, 2) +#define SCULLV_IOCGORDER _IOR(SCULLV_IOC_MAGIC, 3, int) +#define SCULLV_IOCQORDER _IO(SCULLV_IOC_MAGIC, 4) +#define SCULLV_IOCXORDER _IOWR(SCULLV_IOC_MAGIC, 5, int) +#define SCULLV_IOCHORDER _IO(SCULLV_IOC_MAGIC, 6) +#define SCULLV_IOCSQSET _IOW(SCULLV_IOC_MAGIC, 7, int) +#define SCULLV_IOCTQSET _IO(SCULLV_IOC_MAGIC, 8) +#define SCULLV_IOCGQSET _IOR(SCULLV_IOC_MAGIC, 9, int) +#define SCULLV_IOCQQSET _IO(SCULLV_IOC_MAGIC, 10) +#define SCULLV_IOCXQSET _IOWR(SCULLV_IOC_MAGIC,11, int) +#define SCULLV_IOCHQSET _IO(SCULLV_IOC_MAGIC, 12) + +#define SCULLV_IOC_MAXNR 12 + + + diff --git a/scullv/scullv_load b/scullv/scullv_load new file mode 100644 index 0000000..9b26276 --- /dev/null +++ b/scullv/scullv_load @@ -0,0 +1,30 @@ +#!/bin/sh +module="scullv" +device="scullv" +mode="664" + +# Group: since distributions do it differently, look for wheel or use staff +if grep '^staff:' /etc/group > /dev/null; then + group="staff" +else + group="wheel" +fi + +# remove stale nodes +rm -f /dev/${device}? + +# invoke insmod with all arguments we got +# and use a pathname, as newer modutils don't look in . by default +/sbin/insmod -f ./$module.ko $* || exit 1 + +major=`cat /proc/devices | awk "\\$2==\"$module\" {print \\$1}"` + +mknod /dev/${device}0 c $major 0 +mknod /dev/${device}1 c $major 1 +mknod /dev/${device}2 c $major 2 +mknod /dev/${device}3 c $major 3 +ln -sf ${device}0 /dev/${device} + +# give appropriate group/permissions +chgrp $group /dev/${device}[0-3] +chmod $mode /dev/${device}[0-3] diff --git a/scullv/scullv_unload b/scullv/scullv_unload new file mode 100644 index 0000000..13608e8 --- /dev/null +++ b/scullv/scullv_unload @@ -0,0 +1,11 @@ +#!/bin/sh +module="scullv" +device="scullv" + +# invoke rmmod with all arguments we got +/sbin/rmmod $module $* || exit 1 + +# remove nodes +rm -f /dev/${device}[0-3] /dev/${device} + +exit 0 diff --git a/short/Makefile b/short/Makefile new file mode 100644 index 0000000..1655b3f --- /dev/null +++ b/short/Makefile @@ -0,0 +1,40 @@ +# Comment/uncomment the following line to disable/enable debugging +#DEBUG = y + + +# Add your debugging flag (or not) to CFLAGS +ifeq ($(DEBUG),y) + DEBFLAGS = -O -g -DSHORT_DEBUG # "-O" is needed to expand inlines +else + DEBFLAGS = -O2 +endif + +CFLAGS += $(DEBFLAGS) +CFLAGS += -I.. + +ifneq ($(KERNELRELEASE),) +# call from kernel build system + +obj-m := short.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +default: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +endif + + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/short/short.c b/short/short.c new file mode 100644 index 0000000..8bd39ed --- /dev/null +++ b/short/short.c @@ -0,0 +1,692 @@ +/* + * short.c -- Simple Hardware Operations and Raw Tests + * short.c -- also a brief example of interrupt handling ("short int") + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: short.c,v 1.16 2004/10/29 16:45:40 corbet Exp $ + */ + +/* + * FIXME: this driver is not safe with concurrent readers or + * writers. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include <linux/sched.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/delay.h> /* udelay */ +#include <linux/kdev_t.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/poll.h> +#include <linux/wait.h> + +#include <asm/io.h> + +#define SHORT_NR_PORTS 8 /* use 8 ports by default */ + +/* + * all of the parameters have no "short_" prefix, to save typing when + * specifying them at load time + */ +static int major = 0; /* dynamic by default */ +module_param(major, int, 0); + +static int use_mem = 0; /* default is I/O-mapped */ +module_param(use_mem, int, 0); + +/* default is the first printer port on PC's. "short_base" is there too + because it's what we want to use in the code */ +static unsigned long base = 0x378; +unsigned long short_base = 0; +module_param(base, long, 0); + +/* The interrupt line is undefined by default. "short_irq" is as above */ +static int irq = -1; +volatile int short_irq = -1; +module_param(irq, int, 0); + +static int probe = 0; /* select at load time how to probe irq line */ +module_param(probe, int, 0); + +static int wq = 0; /* select at load time whether a workqueue is used */ +module_param(wq, int, 0); + +static int tasklet = 0; /* select whether a tasklet is used */ +module_param(tasklet, int, 0); + +static int share = 0; /* select at load time whether install a shared irq */ +module_param(share, int, 0); + +MODULE_AUTHOR ("Alessandro Rubini"); +MODULE_LICENSE("Dual BSD/GPL"); + + +unsigned long short_buffer = 0; +unsigned long volatile short_head; +volatile unsigned long short_tail; +DECLARE_WAIT_QUEUE_HEAD(short_queue); + +/* Set up our tasklet if we're doing that. */ +void short_do_tasklet(unsigned long); +DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0); + +/* + * Atomicly increment an index into short_buffer + */ +static inline void short_incr_bp(volatile unsigned long *index, int delta) +{ + unsigned long new = *index + delta; + barrier(); /* Don't optimize these two together */ + *index = (new >= (short_buffer + PAGE_SIZE)) ? short_buffer : new; +} + + +/* + * The devices with low minor numbers write/read burst of data to/from + * specific I/O ports (by default the parallel ones). + * + * The device with 128 as minor number returns ascii strings telling + * when interrupts have been received. Writing to the device toggles + * 00/FF on the parallel data lines. If there is a loopback wire, this + * generates interrupts. + */ + +int short_open (struct inode *inode, struct file *filp) +{ + extern struct file_operations short_i_fops; + + if (iminor (inode) & 0x80) + filp->f_op = &short_i_fops; /* the interrupt-driven node */ + return 0; +} + + +int short_release (struct inode *inode, struct file *filp) +{ + return 0; +} + + +/* first, the port-oriented device */ + +enum short_modes {SHORT_DEFAULT=0, SHORT_PAUSE, SHORT_STRING, SHORT_MEMORY}; + +ssize_t do_short_read (struct inode *inode, struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + int retval = count, minor = iminor (inode); + unsigned long port = short_base + (minor&0x0f); + void *address = (void *) short_base + (minor&0x0f); + int mode = (minor&0x70) >> 4; + unsigned char *kbuf = kmalloc(count, GFP_KERNEL), *ptr; + + if (!kbuf) + return -ENOMEM; + ptr = kbuf; + + if (use_mem) + mode = SHORT_MEMORY; + + switch(mode) { + case SHORT_STRING: + insb(port, ptr, count); + rmb(); + break; + + case SHORT_DEFAULT: + while (count--) { + *(ptr++) = inb(port); + rmb(); + } + break; + + case SHORT_MEMORY: + while (count--) { + *ptr++ = ioread8(address); + rmb(); + } + break; + case SHORT_PAUSE: + while (count--) { + *(ptr++) = inb_p(port); + rmb(); + } + break; + + default: /* no more modes defined by now */ + retval = -EINVAL; + break; + } + if ((retval > 0) && copy_to_user(buf, kbuf, retval)) + retval = -EFAULT; + kfree(kbuf); + return retval; +} + + +/* + * Version-specific methods for the fops structure. FIXME don't need anymore. + */ +ssize_t short_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) +{ + return do_short_read(filp->f_dentry->d_inode, filp, buf, count, f_pos); +} + + + +ssize_t do_short_write (struct inode *inode, struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + int retval = count, minor = iminor(inode); + unsigned long port = short_base + (minor&0x0f); + void *address = (void *) short_base + (minor&0x0f); + int mode = (minor&0x70) >> 4; + unsigned char *kbuf = kmalloc(count, GFP_KERNEL), *ptr; + + if (!kbuf) + return -ENOMEM; + if (copy_from_user(kbuf, buf, count)) + return -EFAULT; + ptr = kbuf; + + if (use_mem) + mode = SHORT_MEMORY; + + switch(mode) { + case SHORT_PAUSE: + while (count--) { + outb_p(*(ptr++), port); + wmb(); + } + break; + + case SHORT_STRING: + outsb(port, ptr, count); + wmb(); + break; + + case SHORT_DEFAULT: + while (count--) { + outb(*(ptr++), port); + wmb(); + } + break; + + case SHORT_MEMORY: + while (count--) { + iowrite8(*ptr++, address); + wmb(); + } + break; + + default: /* no more modes defined by now */ + retval = -EINVAL; + break; + } + kfree(kbuf); + return retval; +} + + +ssize_t short_write(struct file *filp, const char __user *buf, size_t count, + loff_t *f_pos) +{ + return do_short_write(filp->f_dentry->d_inode, filp, buf, count, f_pos); +} + + + + +unsigned int short_poll(struct file *filp, poll_table *wait) +{ + return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM; +} + + + + + + +struct file_operations short_fops = { + .owner = THIS_MODULE, + .read = short_read, + .write = short_write, + .poll = short_poll, + .open = short_open, + .release = short_release, +}; + +/* then, the interrupt-related device */ + +ssize_t short_i_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos) +{ + int count0; + DEFINE_WAIT(wait); + + while (short_head == short_tail) { + prepare_to_wait(&short_queue, &wait, TASK_INTERRUPTIBLE); + if (short_head == short_tail) + schedule(); + finish_wait(&short_queue, &wait); + if (signal_pending (current)) /* a signal arrived */ + return -ERESTARTSYS; /* tell the fs layer to handle it */ + } + /* count0 is the number of readable data bytes */ + count0 = short_head - short_tail; + if (count0 < 0) /* wrapped */ + count0 = short_buffer + PAGE_SIZE - short_tail; + if (count0 < count) count = count0; + + if (copy_to_user(buf, (char *)short_tail, count)) + return -EFAULT; + short_incr_bp (&short_tail, count); + return count; +} + +ssize_t short_i_write (struct file *filp, const char __user *buf, size_t count, + loff_t *f_pos) +{ + int written = 0, odd = *f_pos & 1; + unsigned long port = short_base; /* output to the parallel data latch */ + void *address = (void *) short_base; + + if (use_mem) { + while (written < count) + iowrite8(0xff * ((++written + odd) & 1), address); + } else { + while (written < count) + outb(0xff * ((++written + odd) & 1), port); + } + + *f_pos += count; + return written; +} + + + + +struct file_operations short_i_fops = { + .owner = THIS_MODULE, + .read = short_i_read, + .write = short_i_write, + .open = short_open, + .release = short_release, +}; + +irqreturn_t short_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct timeval tv; + int written; + + do_gettimeofday(&tv); + + /* Write a 16 byte record. Assume PAGE_SIZE is a multiple of 16 */ + written = sprintf((char *)short_head,"%08u.%06u\n", + (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec)); + BUG_ON(written != 16); + short_incr_bp(&short_head, written); + wake_up_interruptible(&short_queue); /* awake any reading process */ + return IRQ_HANDLED; +} + +/* + * The following two functions are equivalent to the previous one, + * but split in top and bottom half. First, a few needed variables + */ + +#define NR_TIMEVAL 512 /* length of the array of time values */ + +struct timeval tv_data[NR_TIMEVAL]; /* too lazy to allocate it */ +volatile struct timeval *tv_head=tv_data; +volatile struct timeval *tv_tail=tv_data; + +static struct work_struct short_wq; + + +int short_wq_count = 0; + +/* + * Increment a circular buffer pointer in a way that nobody sees + * an intermediate value. + */ +static inline void short_incr_tv(volatile struct timeval **tvp) +{ + if (*tvp == (tv_data + NR_TIMEVAL - 1)) + *tvp = tv_data; /* Wrap */ + else + (*tvp)++; +} + + + +void short_do_tasklet (unsigned long unused) +{ + int savecount = short_wq_count, written; + short_wq_count = 0; /* we have already been removed from the queue */ + /* + * The bottom half reads the tv array, filled by the top half, + * and prints it to the circular text buffer, which is then consumed + * by reading processes + */ + + /* First write the number of interrupts that occurred before this bh */ + written = sprintf((char *)short_head,"bh after %6i\n",savecount); + short_incr_bp(&short_head, written); + + /* + * Then, write the time values. Write exactly 16 bytes at a time, + * so it aligns with PAGE_SIZE + */ + + do { + written = sprintf((char *)short_head,"%08u.%06u\n", + (int)(tv_tail->tv_sec % 100000000), + (int)(tv_tail->tv_usec)); + short_incr_bp(&short_head, written); + short_incr_tv(&tv_tail); + } while (tv_tail != tv_head); + + wake_up_interruptible(&short_queue); /* awake any reading process */ +} + + +irqreturn_t short_wq_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + /* Grab the current time information. */ + do_gettimeofday((struct timeval *) tv_head); + short_incr_tv(&tv_head); + + /* Queue the bh. Don't worry about multiple enqueueing */ + schedule_work(&short_wq); + + short_wq_count++; /* record that an interrupt arrived */ + return IRQ_HANDLED; +} + + +/* + * Tasklet top half + */ + +irqreturn_t short_tl_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + do_gettimeofday((struct timeval *) tv_head); /* cast to stop 'volatile' warning */ + short_incr_tv(&tv_head); + tasklet_schedule(&short_tasklet); + short_wq_count++; /* record that an interrupt arrived */ + return IRQ_HANDLED; +} + + + + +irqreturn_t short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + int value, written; + struct timeval tv; + + /* If it wasn't short, return immediately */ + value = inb(short_base); + if (!(value & 0x80)) + return IRQ_NONE; + + /* clear the interrupting bit */ + outb(value & 0x7F, short_base); + + /* the rest is unchanged */ + + do_gettimeofday(&tv); + written = sprintf((char *)short_head,"%08u.%06u\n", + (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec)); + short_incr_bp(&short_head, written); + wake_up_interruptible(&short_queue); /* awake any reading process */ + return IRQ_HANDLED; +} + +void short_kernelprobe(void) +{ + int count = 0; + do { + unsigned long mask; + + mask = probe_irq_on(); + outb_p(0x10,short_base+2); /* enable reporting */ + outb_p(0x00,short_base); /* clear the bit */ + outb_p(0xFF,short_base); /* set the bit: interrupt! */ + outb_p(0x00,short_base+2); /* disable reporting */ + udelay(5); /* give it some time */ + short_irq = probe_irq_off(mask); + + if (short_irq == 0) { /* none of them? */ + printk(KERN_INFO "short: no irq reported by probe\n"); + short_irq = -1; + } + /* + * if more than one line has been activated, the result is + * negative. We should service the interrupt (no need for lpt port) + * and loop over again. Loop at most five times, then give up + */ + } while (short_irq < 0 && count++ < 5); + if (short_irq < 0) + printk("short: probe failed %i times, giving up\n", count); +} + +irqreturn_t short_probing(int irq, void *dev_id, struct pt_regs *regs) +{ + if (short_irq == 0) short_irq = irq; /* found */ + if (short_irq != irq) short_irq = -irq; /* ambiguous */ + return IRQ_HANDLED; +} + +void short_selfprobe(void) +{ + int trials[] = {3, 5, 7, 9, 0}; + int tried[] = {0, 0, 0, 0, 0}; + int i, count = 0; + + /* + * install the probing handler for all possible lines. Remember + * the result (0 for success, or -EBUSY) in order to only free + * what has been acquired + */ + for (i = 0; trials[i]; i++) + tried[i] = request_irq(trials[i], short_probing, + SA_INTERRUPT, "short probe", NULL); + + do { + short_irq = 0; /* none got, yet */ + outb_p(0x10,short_base+2); /* enable */ + outb_p(0x00,short_base); + outb_p(0xFF,short_base); /* toggle the bit */ + outb_p(0x00,short_base+2); /* disable */ + udelay(5); /* give it some time */ + + /* the value has been set by the handler */ + if (short_irq == 0) { /* none of them? */ + printk(KERN_INFO "short: no irq reported by probe\n"); + } + /* + * If more than one line has been activated, the result is + * negative. We should service the interrupt (but the lpt port + * doesn't need it) and loop over again. Do it at most 5 times + */ + } while (short_irq <=0 && count++ < 5); + + /* end of loop, uninstall the handler */ + for (i = 0; trials[i]; i++) + if (tried[i] == 0) + free_irq(trials[i], NULL); + + if (short_irq < 0) + printk("short: probe failed %i times, giving up\n", count); +} + + + +/* Finally, init and cleanup */ + +int short_init(void) +{ + int result; + + /* + * first, sort out the base/short_base ambiguity: we'd better + * use short_base in the code, for clarity, but allow setting + * just "base" at load time. Same for "irq". + */ + short_base = base; + short_irq = irq; + + /* Get our needed resources. */ + if (!use_mem) { + if (! request_region(short_base, SHORT_NR_PORTS, "short")) { + printk(KERN_INFO "short: can't get I/O port address 0x%lx\n", + short_base); + return -ENODEV; + } + + } else { + if (! request_mem_region(short_base, SHORT_NR_PORTS, "short")) { + printk(KERN_INFO "short: can't get I/O mem address 0x%lx\n", + short_base); + return -ENODEV; + } + + /* also, ioremap it */ + short_base = (unsigned long) ioremap(short_base, SHORT_NR_PORTS); + /* Hmm... we should check the return value */ + } + /* Here we register our device - should not fail thereafter */ + result = register_chrdev(major, "short", &short_fops); + if (result < 0) { + printk(KERN_INFO "short: can't get major number\n"); + release_region(short_base,SHORT_NR_PORTS); /* FIXME - use-mem case? */ + return result; + } + if (major == 0) major = result; /* dynamic */ + + short_buffer = __get_free_pages(GFP_KERNEL,0); /* never fails */ /* FIXME */ + short_head = short_tail = short_buffer; + + /* + * Fill the workqueue structure, used for the bottom half handler. + * The cast is there to prevent warnings about the type of the + * (unused) argument. + */ + /* this line is in short_init() */ + INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL); + + /* + * Now we deal with the interrupt: either kernel-based + * autodetection, DIY detection or default number + */ + + if (short_irq < 0 && probe == 1) + short_kernelprobe(); + + if (short_irq < 0 && probe == 2) + short_selfprobe(); + + if (short_irq < 0) /* not yet specified: force the default on */ + switch(short_base) { + case 0x378: short_irq = 7; break; + case 0x278: short_irq = 2; break; + case 0x3bc: short_irq = 5; break; + } + + /* + * If shared has been specified, installed the shared handler + * instead of the normal one. Do it first, before a -EBUSY will + * force short_irq to -1. + */ + if (short_irq >= 0 && share > 0) { + result = request_irq(short_irq, short_sh_interrupt, + SA_SHIRQ | SA_INTERRUPT,"short", + short_sh_interrupt); + if (result) { + printk(KERN_INFO "short: can't get assigned irq %i\n", short_irq); + short_irq = -1; + } + else { /* actually enable it -- assume this *is* a parallel port */ + outb(0x10, short_base+2); + } + return 0; /* the rest of the function only installs handlers */ + } + + if (short_irq >= 0) { + result = request_irq(short_irq, short_interrupt, + SA_INTERRUPT, "short", NULL); + if (result) { + printk(KERN_INFO "short: can't get assigned irq %i\n", + short_irq); + short_irq = -1; + } + else { /* actually enable it -- assume this *is* a parallel port */ + outb(0x10,short_base+2); + } + } + + /* + * Ok, now change the interrupt handler if using top/bottom halves + * has been requested + */ + if (short_irq >= 0 && (wq + tasklet) > 0) { + free_irq(short_irq,NULL); + result = request_irq(short_irq, + tasklet ? short_tl_interrupt : + short_wq_interrupt, + SA_INTERRUPT,"short-bh", NULL); + if (result) { + printk(KERN_INFO "short-bh: can't get assigned irq %i\n", + short_irq); + short_irq = -1; + } + } + + return 0; +} + +void short_cleanup(void) +{ + if (short_irq >= 0) { + outb(0x0, short_base + 2); /* disable the interrupt */ + if (!share) free_irq(short_irq, NULL); + else free_irq(short_irq, short_sh_interrupt); + } + /* Make sure we don't leave work queue/tasklet functions running */ + if (tasklet) + tasklet_disable(&short_tasklet); + else + flush_scheduled_work(); + unregister_chrdev(major, "short"); + if (use_mem) { + iounmap((void __iomem *)short_base); + release_mem_region(short_base, SHORT_NR_PORTS); + } else { + release_region(short_base,SHORT_NR_PORTS); + } + if (short_buffer) free_page(short_buffer); +} + +module_init(short_init); +module_exit(short_cleanup); diff --git a/short/short_load b/short/short_load new file mode 100644 index 0000000..407846c --- /dev/null +++ b/short/short_load @@ -0,0 +1,61 @@ +#!/bin/sh +module="short" +device="short" +mode="664" + +# Group: since distributions do it differently, look for wheel or use staff +if grep '^staff:' /etc/group > /dev/null; then + group="staff" +else + group="wheel" +fi + + +# invoke insmod with all arguments we got +# and use a pathname, as newer modutils don't look in . by default +/sbin/insmod ./$module.ko $* || exit 1 + +major=`cat /proc/devices | awk "\\$2==\"$module\" {print \\$1}"` + +# Create 8 entry points, as SHORT_NR_PORTS is 8 by default +rm -f /dev/${device}[0-7] +mknod /dev/${device}0 c $major 0 +mknod /dev/${device}1 c $major 1 +mknod /dev/${device}2 c $major 2 +mknod /dev/${device}3 c $major 3 +mknod /dev/${device}4 c $major 4 +mknod /dev/${device}5 c $major 5 +mknod /dev/${device}6 c $major 6 +mknod /dev/${device}7 c $major 7 + +rm -f /dev/${device}[0-3][ps] +mknod /dev/${device}0p c $major 16 +mknod /dev/${device}1p c $major 17 +mknod /dev/${device}2p c $major 18 +mknod /dev/${device}3p c $major 19 +mknod /dev/${device}4p c $major 20 +mknod /dev/${device}5p c $major 21 +mknod /dev/${device}6p c $major 22 +mknod /dev/${device}7p c $major 23 + +mknod /dev/${device}0s c $major 32 +mknod /dev/${device}1s c $major 33 +mknod /dev/${device}2s c $major 34 +mknod /dev/${device}3s c $major 35 +mknod /dev/${device}4s c $major 36 +mknod /dev/${device}5s c $major 37 +mknod /dev/${device}6s c $major 38 +mknod /dev/${device}7s c $major 39 + +rm -f /dev/${device}int /dev/${device}print +mknod /dev/${device}int c $major 128 +mknod /dev/${device}print c $major 129 + +chgrp $group /dev/${device}[0-7] /dev/${device}[0-7][ps] /dev/${device}int +chmod $mode /dev/${device}[0-7] /dev/${device}[0-7][ps] /dev/${device}int + + + + + + diff --git a/short/short_unload b/short/short_unload new file mode 100644 index 0000000..7cf64dc --- /dev/null +++ b/short/short_unload @@ -0,0 +1,16 @@ +#!/bin/sh +module="short" +device="short" + +# invoke rmmod with all arguments we got +/sbin/rmmod $module $* || exit 1 + +# Remove stale nodes + +rm -f /dev/${device}[0-7] /dev/${device}[0-7][ps] \ + /dev/${device}int /dev/${device}print + + + + + diff --git a/shortprint/Makefile b/shortprint/Makefile new file mode 100644 index 0000000..42a9b53 --- /dev/null +++ b/shortprint/Makefile @@ -0,0 +1,31 @@ +# Comment/uncomment the following line to disable/enable debugging +#DEBUG = y + +CFLAGS += -O2 -I.. + +ifneq ($(KERNELRELEASE),) +# call from kernel build system + +obj-m := shortprint.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +default: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +endif + + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/shortprint/shortprint.c b/shortprint/shortprint.c new file mode 100644 index 0000000..d7c8520 --- /dev/null +++ b/shortprint/shortprint.c @@ -0,0 +1,521 @@ +/* + * A version of the "short" driver which drives a parallel printer directly, + * with a lot of simplifying assumptions. + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: shortprint.c,v 1.4 2004/09/26 08:01:04 gregkh Exp $ + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> + +#include <linux/sched.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/delay.h> /* udelay */ +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/timer.h> +#include <linux/poll.h> + +#include <asm/io.h> +#include <asm/semaphore.h> +#include <asm/atomic.h> + +#include "shortprint.h" + +#define SHORTP_NR_PORTS 3 + +/* + * all of the parameters have no "shortp_" prefix, to save typing when + * specifying them at load time + */ +static int major = 0; /* dynamic by default */ +module_param(major, int, 0); + +/* default is the first printer port on PC's. "shortp_base" is there too + because it's what we want to use in the code */ +static unsigned long base = 0x378; +unsigned long shortp_base = 0; +module_param(base, long, 0); + +/* The interrupt line is undefined by default. "shortp_irq" is as above */ +static int irq = -1; +static int shortp_irq = -1; +module_param(irq, int, 0); + +/* Microsecond delay around strobe. */ +static int delay = 0; +static int shortp_delay; +module_param(delay, int, 0); + +MODULE_AUTHOR ("Jonathan Corbet"); +MODULE_LICENSE("Dual BSD/GPL"); + +/* + * Forwards. + */ +static void shortp_cleanup(void); +static void shortp_timeout(unsigned long unused); + +/* + * Input is managed through a simple circular buffer which, among other things, + * is allowed to overrun if the reader isn't fast enough. That makes life simple + * on the "read" interrupt side, where we don't want to block. + */ +static unsigned long shortp_in_buffer = 0; +static unsigned long volatile shortp_in_head; +static volatile unsigned long shortp_in_tail; +DECLARE_WAIT_QUEUE_HEAD(shortp_in_queue); +static struct timeval shortp_tv; /* When the interrupt happened. */ + +/* + * Atomicly increment an index into shortp_in_buffer + */ +static inline void shortp_incr_bp(volatile unsigned long *index, int delta) +{ + unsigned long new = *index + delta; + barrier (); /* Don't optimize these two together */ + *index = (new >= (shortp_in_buffer + PAGE_SIZE)) ? shortp_in_buffer : new; +} + + +/* + * On the write side we have to be more careful, since we don't want to drop + * data. The semaphore is used to serialize write-side access to the buffer; + * there is only one consumer, so read-side access is unregulated. The + * wait queue will be awakened when space becomes available in the buffer. + */ +static unsigned char *shortp_out_buffer = NULL; +static volatile unsigned char *shortp_out_head, *shortp_out_tail; +static struct semaphore shortp_out_sem; +static DECLARE_WAIT_QUEUE_HEAD(shortp_out_queue); + +/* + * Feeding the output queue to the device is handled by way of a + * workqueue. + */ +static void shortp_do_work(void *); +static DECLARE_WORK(shortp_work, shortp_do_work, NULL); +static struct workqueue_struct *shortp_workqueue; + +/* + * Available space in the output buffer; should be called with the semaphore + * held. Returns contiguous space, so caller need not worry about wraps. + */ +static inline int shortp_out_space(void) +{ + if (shortp_out_head >= shortp_out_tail) { + int space = PAGE_SIZE - (shortp_out_head - shortp_out_buffer); + return (shortp_out_tail == shortp_out_buffer) ? space - 1 : space; + } else + return (shortp_out_tail - shortp_out_head) - 1; +} + +static inline void shortp_incr_out_bp(volatile unsigned char **bp, int incr) +{ + unsigned char *new = (unsigned char *) *bp + incr; + if (new >= (shortp_out_buffer + PAGE_SIZE)) + new -= PAGE_SIZE; + *bp = new; +} + +/* + * The output "process" is controlled by a spin lock; decisions on + * shortp_output_active or manipulation of shortp_out_tail require + * that this lock be held. + */ +static spinlock_t shortp_out_lock; +volatile static int shortp_output_active; +DECLARE_WAIT_QUEUE_HEAD(shortp_empty_queue); /* waked when queue empties */ + +/* + * When output is active, the timer is too, in case we miss interrupts. Hold + * shortp_out_lock if you mess with the timer. + */ +static struct timer_list shortp_timer; +#define TIMEOUT 5*HZ /* Wait a long time */ + + +/* + * Open the device. + */ +static int shortp_open(struct inode *inode, struct file *filp) +{ + return 0; +} + + +static int shortp_release(struct inode *inode, struct file *filp) +{ + /* Wait for any pending output to complete */ + wait_event_interruptible(shortp_empty_queue, shortp_output_active==0); + + return 0; +} + + + +static unsigned int shortp_poll(struct file *filp, poll_table *wait) +{ + return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM; +} + + + +/* + * The read routine, which doesn't return data from the device; instead, it + * returns timing information just like the "short" device. + */ +static ssize_t shortp_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) +{ + int count0; + DEFINE_WAIT(wait); + + while (shortp_in_head == shortp_in_tail) { + prepare_to_wait(&shortp_in_queue, &wait, TASK_INTERRUPTIBLE); + if (shortp_in_head == shortp_in_tail) + schedule(); + finish_wait(&shortp_in_queue, &wait); + if (signal_pending (current)) /* a signal arrived */ + return -ERESTARTSYS; /* tell the fs layer to handle it */ + } + + /* count0 is the number of readable data bytes */ + count0 = shortp_in_head - shortp_in_tail; + if (count0 < 0) /* wrapped */ + count0 = shortp_in_buffer + PAGE_SIZE - shortp_in_tail; + if (count0 < count) + count = count0; + + if (copy_to_user(buf, (char *)shortp_in_tail, count)) + return -EFAULT; + shortp_incr_bp(&shortp_in_tail, count); + return count; +} + + +/* + * Wait for the printer to be ready; this can sleep. + */ +static void shortp_wait(void) +{ + if ((inb(shortp_base + SP_STATUS) & SP_SR_BUSY) == 0) { + printk(KERN_INFO "shortprint: waiting for printer busy\n"); + printk(KERN_INFO "Status is 0x%x\n", inb(shortp_base + SP_STATUS)); + while ((inb(shortp_base + SP_STATUS) & SP_SR_BUSY) == 0) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(10*HZ); + } + } +} + + +/* + * Write the next character from the buffer. There should *be* a next + * character... The spinlock should be held when this routine is called. + */ +static void shortp_do_write(void) +{ + unsigned char cr = inb(shortp_base + SP_CONTROL); + + /* Something happened; reset the timer */ + mod_timer(&shortp_timer, jiffies + TIMEOUT); + + /* Strobe a byte out to the device */ + outb_p(*shortp_out_tail, shortp_base+SP_DATA); + shortp_incr_out_bp(&shortp_out_tail, 1); + if (shortp_delay) + udelay(shortp_delay); + outb_p(cr | SP_CR_STROBE, shortp_base+SP_CONTROL); + if (shortp_delay) + udelay(shortp_delay); + outb_p(cr & ~SP_CR_STROBE, shortp_base+SP_CONTROL); +} + + +/* + * Start output; call under lock. + */ +static void shortp_start_output(void) +{ + if (shortp_output_active) /* Should never happen */ + return; + + /* Set up our 'missed interrupt' timer */ + shortp_output_active = 1; + shortp_timer.expires = jiffies + TIMEOUT; + add_timer(&shortp_timer); + + /* And get the process going. */ + queue_work(shortp_workqueue, &shortp_work); +} + + +/* + * Write to the device. + */ +static ssize_t shortp_write(struct file *filp, const char __user *buf, size_t count, + loff_t *f_pos) +{ + int space, written = 0; + unsigned long flags; + /* + * Take and hold the semaphore for the entire duration of the operation. The + * consumer side ignores it, and it will keep other data from interleaving + * with ours. + */ + if (down_interruptible(&shortp_out_sem)) + return -ERESTARTSYS; + /* + * Out with the data. + */ + while (written < count) { + /* Hang out until some buffer space is available. */ + space = shortp_out_space(); + if (space <= 0) { + if (wait_event_interruptible(shortp_out_queue, + (space = shortp_out_space()) > 0)) + goto out; + } + + /* Move data into the buffer. */ + if ((space + written) > count) + space = count - written; + if (copy_from_user((char *) shortp_out_head, buf, space)) { + up(&shortp_out_sem); + return -EFAULT; + } + shortp_incr_out_bp(&shortp_out_head, space); + buf += space; + written += space; + + /* If no output is active, make it active. */ + spin_lock_irqsave(&shortp_out_lock, flags); + if (! shortp_output_active) + shortp_start_output(); + spin_unlock_irqrestore(&shortp_out_lock, flags); + } + +out: + *f_pos += written; + up(&shortp_out_sem); + return written; +} + + +/* + * The bottom-half handler. + */ + + +static void shortp_do_work(void *unused) +{ + int written; + unsigned long flags; + + /* Wait until the device is ready */ + shortp_wait(); + + spin_lock_irqsave(&shortp_out_lock, flags); + + /* Have we written everything? */ + if (shortp_out_head == shortp_out_tail) { /* empty */ + shortp_output_active = 0; + wake_up_interruptible(&shortp_empty_queue); + del_timer(&shortp_timer); + } + /* Nope, write another byte */ + else + shortp_do_write(); + + /* If somebody's waiting, maybe wake them up. */ + if (((PAGE_SIZE + shortp_out_tail - shortp_out_head) % PAGE_SIZE) > SP_MIN_SPACE) { + wake_up_interruptible(&shortp_out_queue); + } + spin_unlock_irqrestore(&shortp_out_lock, flags); + + /* Handle the "read" side operation */ + written = sprintf((char *)shortp_in_head, "%08u.%06u\n", + (int)(shortp_tv.tv_sec % 100000000), + (int)(shortp_tv.tv_usec)); + shortp_incr_bp(&shortp_in_head, written); + wake_up_interruptible(&shortp_in_queue); /* awake any reading process */ +} + + +/* + * The top-half interrupt handler. + */ +static irqreturn_t shortp_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + if (! shortp_output_active) + return IRQ_NONE; + + /* Remember the time, and farm off the rest to the workqueue function */ + do_gettimeofday(&shortp_tv); + queue_work(shortp_workqueue, &shortp_work); + return IRQ_HANDLED; +} + +/* + * Interrupt timeouts. Just because we got a timeout doesn't mean that + * things have gone wrong, however; printers can spend an awful long time + * just thinking about things. + */ +static void shortp_timeout(unsigned long unused) +{ + unsigned long flags; + unsigned char status; + + if (! shortp_output_active) + return; + spin_lock_irqsave(&shortp_out_lock, flags); + status = inb(shortp_base + SP_STATUS); + + /* If the printer is still busy we just reset the timer */ + if ((status & SP_SR_BUSY) == 0 || (status & SP_SR_ACK)) { + shortp_timer.expires = jiffies + TIMEOUT; + add_timer(&shortp_timer); + spin_unlock_irqrestore(&shortp_out_lock, flags); + return; + } + + /* Otherwise we must have dropped an interrupt. */ + spin_unlock_irqrestore(&shortp_out_lock, flags); + shortp_interrupt(shortp_irq, NULL, NULL); +} + + + + + +static struct file_operations shortp_fops = { + .read = shortp_read, + .write = shortp_write, + .open = shortp_open, + .release = shortp_release, + .poll = shortp_poll, + .owner = THIS_MODULE +}; + + + + +/* + * Module initialization + */ + +static int shortp_init(void) +{ + int result; + + /* + * first, sort out the base/shortp_base ambiguity: we'd better + * use shortp_base in the code, for clarity, but allow setting + * just "base" at load time. Same for "irq". + */ + shortp_base = base; + shortp_irq = irq; + shortp_delay = delay; + + /* Get our needed resources. */ + if (! request_region(shortp_base, SHORTP_NR_PORTS, "shortprint")) { + printk(KERN_INFO "shortprint: can't get I/O port address 0x%lx\n", + shortp_base); + return -ENODEV; + } + + /* Register the device */ + result = register_chrdev(major, "shortprint", &shortp_fops); + if (result < 0) { + printk(KERN_INFO "shortp: can't get major number\n"); + release_region(shortp_base, SHORTP_NR_PORTS); + return result; + } + if (major == 0) + major = result; /* dynamic */ + + /* Initialize the input buffer. */ + shortp_in_buffer = __get_free_pages(GFP_KERNEL, 0); /* never fails */ + shortp_in_head = shortp_in_tail = shortp_in_buffer; + + /* And the output buffer. */ + shortp_out_buffer = (unsigned char *) __get_free_pages(GFP_KERNEL, 0); + shortp_out_head = shortp_out_tail = shortp_out_buffer; + sema_init(&shortp_out_sem, 1); + + /* And the output info */ + shortp_output_active = 0; + spin_lock_init(&shortp_out_lock); + init_timer(&shortp_timer); + shortp_timer.function = shortp_timeout; + shortp_timer.data = 0; + + /* Set up our workqueue. */ + shortp_workqueue = create_singlethread_workqueue("shortprint"); + + /* If no IRQ was explicitly requested, pick a default */ + if (shortp_irq < 0) + switch(shortp_base) { + case 0x378: shortp_irq = 7; break; + case 0x278: shortp_irq = 2; break; + case 0x3bc: shortp_irq = 5; break; + } + + /* Request the IRQ */ + result = request_irq(shortp_irq, shortp_interrupt, 0, "shortprint", NULL); + if (result) { + printk(KERN_INFO "shortprint: can't get assigned irq %i\n", + shortp_irq); + shortp_irq = -1; + shortp_cleanup (); + return result; + } + + /* Initialize the control register, turning on interrupts. */ + outb(SP_CR_IRQ | SP_CR_SELECT | SP_CR_INIT, shortp_base + SP_CONTROL); + + return 0; +} + +static void shortp_cleanup(void) +{ + /* Return the IRQ if we have one */ + if (shortp_irq >= 0) { + outb(0x0, shortp_base + SP_CONTROL); /* disable the interrupt */ + free_irq(shortp_irq, NULL); + } + + /* All done with the device */ + unregister_chrdev(major, "shortprint"); + release_region(shortp_base,SHORTP_NR_PORTS); + + /* Don't leave any timers floating around. Note that any active output + is effectively stopped by turning off the interrupt */ + if (shortp_output_active) + del_timer_sync (&shortp_timer); + flush_workqueue(shortp_workqueue); + destroy_workqueue(shortp_workqueue); + + if (shortp_in_buffer) + free_page(shortp_in_buffer); + if (shortp_out_buffer) + free_page((unsigned long) shortp_out_buffer); +} + +module_init(shortp_init); +module_exit(shortp_cleanup); diff --git a/shortprint/shortprint.h b/shortprint/shortprint.h new file mode 100644 index 0000000..1531025 --- /dev/null +++ b/shortprint/shortprint.h @@ -0,0 +1,46 @@ +/* + * Useful info describing the parallel port device. + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + */ + +/* + * Register offsets + */ +#define SP_DATA 0x00 +#define SP_STATUS 0x01 +#define SP_CONTROL 0x02 +#define SP_NPORTS 3 + +/* + * Status register bits. + */ +#define SP_SR_BUSY 0x80 +#define SP_SR_ACK 0x40 +#define SP_SR_PAPER 0x20 +#define SP_SR_ONLINE 0x10 +#define SP_SR_ERR 0x08 + +/* + * Control register. + */ +#define SP_CR_IRQ 0x10 +#define SP_CR_SELECT 0x08 +#define SP_CR_INIT 0x04 +#define SP_CR_AUTOLF 0x02 +#define SP_CR_STROBE 0x01 + +/* + * Minimum space before waking up a writer. + */ +#define SP_MIN_SPACE PAGE_SIZE/2 diff --git a/shortprint/shortprint_load b/shortprint/shortprint_load new file mode 100644 index 0000000..4b08609 --- /dev/null +++ b/shortprint/shortprint_load @@ -0,0 +1,31 @@ +#!/bin/sh +module="shortprint" +device="shortprint" +mode="666" + +# Group: since distributions do it differently, look for wheel or use staff +if grep '^staff:' /etc/group > /dev/null; then + group="staff" +else + group="wheel" +fi + + +# invoke insmod with all arguments we got +# and use a pathname, as newer modutils don't look in . by default +/sbin/insmod -f ./$module.ko $* || exit 1 + +major=`cat /proc/devices | awk "\\$2==\"$module\" {print \\$1}"` + +# Create 8 entry points, as SHORT_NR_PORTS is 8 by default +rm -f /dev/${device} +mknod /dev/${device} c $major 0 + +chgrp $group /dev/${device} +chmod $mode /dev/${device} + + + + + + diff --git a/shortprint/shortprint_unload b/shortprint/shortprint_unload new file mode 100644 index 0000000..88e62a7 --- /dev/null +++ b/shortprint/shortprint_unload @@ -0,0 +1,15 @@ +#!/bin/sh +module="shortprint" +device="shortprint" + +# invoke rmmod with all arguments we got +/sbin/rmmod $module $* || exit 1 + +# Remove stale nodes +rm -f /dev/${device} + + + + + + diff --git a/simple/Makefile b/simple/Makefile new file mode 100644 index 0000000..429f743 --- /dev/null +++ b/simple/Makefile @@ -0,0 +1,39 @@ +# Comment/uncomment the following line to disable/enable debugging +#DEBUG = y + +# Add your debugging flag (or not) to CFLAGS +ifeq ($(DEBUG),y) + DEBFLAGS = -O -g # "-O" is needed to expand inlines +else + DEBFLAGS = -O2 +endif + +CFLAGS += $(DEBFLAGS) -I$(LDDINCDIR) + +ifneq ($(KERNELRELEASE),) +# call from kernel build system + +obj-m := simple.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +default: + $(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINCDIR=$(PWD)/../include modules + +endif + + + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/simple/simple.c b/simple/simple.c new file mode 100644 index 0000000..e6860c7 --- /dev/null +++ b/simple/simple.c @@ -0,0 +1,235 @@ +/* + * Simple - REALLY simple memory mapping demonstration. + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: simple.c,v 1.12 2005/01/31 16:15:31 rubini Exp $ + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include <linux/kernel.h> /* printk() */ +#include <linux/slab.h> /* kmalloc() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/types.h> /* size_t */ +#include <linux/mm.h> +#include <linux/kdev_t.h> +#include <asm/page.h> +#include <linux/cdev.h> + +#include <linux/device.h> + +static int simple_major = 0; +module_param(simple_major, int, 0); +MODULE_AUTHOR("Jonathan Corbet"); +MODULE_LICENSE("Dual BSD/GPL"); + +/* + * Open the device; in fact, there's nothing to do here. + */ +static int simple_open (struct inode *inode, struct file *filp) +{ + return 0; +} + + +/* + * Closing is just as simpler. + */ +static int simple_release(struct inode *inode, struct file *filp) +{ + return 0; +} + + + +/* + * Common VMA ops. + */ + +void simple_vma_open(struct vm_area_struct *vma) +{ + printk(KERN_NOTICE "Simple VMA open, virt %lx, phys %lx\n", + vma->vm_start, vma->vm_pgoff << PAGE_SHIFT); +} + +void simple_vma_close(struct vm_area_struct *vma) +{ + printk(KERN_NOTICE "Simple VMA close.\n"); +} + + +/* + * The remap_pfn_range version of mmap. This one is heavily borrowed + * from drivers/char/mem.c. + */ + +static struct vm_operations_struct simple_remap_vm_ops = { + .open = simple_vma_open, + .close = simple_vma_close, +}; + +static int simple_remap_mmap(struct file *filp, struct vm_area_struct *vma) +{ + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) + return -EAGAIN; + + vma->vm_ops = &simple_remap_vm_ops; + simple_vma_open(vma); + return 0; +} + + + +/* + * The nopage version. + */ +struct page *simple_vma_nopage(struct vm_area_struct *vma, + unsigned long address, int *type) +{ + struct page *pageptr; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + unsigned long physaddr = address - vma->vm_start + offset; + unsigned long pageframe = physaddr >> PAGE_SHIFT; + +// Eventually remove these printks + printk (KERN_NOTICE "---- Nopage, off %lx phys %lx\n", offset, physaddr); + printk (KERN_NOTICE "VA is %p\n", __va (physaddr)); + printk (KERN_NOTICE "Page at %p\n", virt_to_page (__va (physaddr))); + if (!pfn_valid(pageframe)) + return NOPAGE_SIGBUS; + pageptr = pfn_to_page(pageframe); + printk (KERN_NOTICE "page->index = %ld mapping %p\n", pageptr->index, pageptr->mapping); + printk (KERN_NOTICE "Page frame %ld\n", pageframe); + get_page(pageptr); + if (type) + *type = VM_FAULT_MINOR; + return pageptr; +} + +static struct vm_operations_struct simple_nopage_vm_ops = { + .open = simple_vma_open, + .close = simple_vma_close, + .nopage = simple_vma_nopage, +}; + +static int simple_nopage_mmap(struct file *filp, struct vm_area_struct *vma) +{ + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + + if (offset >= __pa(high_memory) || (filp->f_flags & O_SYNC)) + vma->vm_flags |= VM_IO; + vma->vm_flags |= VM_RESERVED; + + vma->vm_ops = &simple_nopage_vm_ops; + simple_vma_open(vma); + return 0; +} + + +/* + * Set up the cdev structure for a device. + */ +static void simple_setup_cdev(struct cdev *dev, int minor, + struct file_operations *fops) +{ + int err, devno = MKDEV(simple_major, minor); + + cdev_init(dev, fops); + dev->owner = THIS_MODULE; + dev->ops = fops; + err = cdev_add (dev, devno, 1); + /* Fail gracefully if need be */ + if (err) + printk (KERN_NOTICE "Error %d adding simple%d", err, minor); +} + + +/* + * Our various sub-devices. + */ +/* Device 0 uses remap_pfn_range */ +static struct file_operations simple_remap_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .release = simple_release, + .mmap = simple_remap_mmap, +}; + +/* Device 1 uses nopage */ +static struct file_operations simple_nopage_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .release = simple_release, + .mmap = simple_nopage_mmap, +}; + +#define MAX_SIMPLE_DEV 2 + +#if 0 +static struct file_operations *simple_fops[MAX_SIMPLE_DEV] = { + &simple_remap_ops, + &simple_nopage_ops, +}; +#endif + +/* + * We export two simple devices. There's no need for us to maintain any + * special housekeeping info, so we just deal with raw cdevs. + */ +static struct cdev SimpleDevs[MAX_SIMPLE_DEV]; + +/* + * Module housekeeping. + */ +static int simple_init(void) +{ + int result; + dev_t dev = MKDEV(simple_major, 0); + + /* Figure out our device number. */ + if (simple_major) + result = register_chrdev_region(dev, 2, "simple"); + else { + result = alloc_chrdev_region(&dev, 0, 2, "simple"); + simple_major = MAJOR(dev); + } + if (result < 0) { + printk(KERN_WARNING "simple: unable to get major %d\n", simple_major); + return result; + } + if (simple_major == 0) + simple_major = result; + + /* Now set up two cdevs. */ + simple_setup_cdev(SimpleDevs, 0, &simple_remap_ops); + simple_setup_cdev(SimpleDevs + 1, 1, &simple_nopage_ops); + return 0; +} + + +static void simple_cleanup(void) +{ + cdev_del(SimpleDevs); + cdev_del(SimpleDevs + 1); + unregister_chrdev_region(MKDEV(simple_major, 0), 2); +} + + +module_init(simple_init); +module_exit(simple_cleanup); diff --git a/simple/simple_load b/simple/simple_load new file mode 100644 index 0000000..5cd004d --- /dev/null +++ b/simple/simple_load @@ -0,0 +1,26 @@ +#!/bin/sh +module="simple" +device="simple" +mode="664" + +# Group: since distributions do it differently, look for wheel or use staff +if grep '^staff:' /etc/group > /dev/null; then + group="staff" +else + group="wheel" +fi + +# invoke insmod with all arguments we got +# and use a pathname, as newer modutils don't look in . by default +/sbin/insmod -f ./$module.ko $* || exit 1 + +major=`cat /proc/devices | awk "\\$2==\"$module\" {print \\$1}"` + +# Remove stale nodes and replace them, then give gid and perms +# Usually the script is shorter, it's simple that has several devices in it. + +rm -f /dev/${device}[rn] +mknod /dev/${device}r c $major 0 +mknod /dev/${device}n c $major 1 +chgrp $group /dev/${device}[rn] +chmod $mode /dev/${device}[rn] diff --git a/simple/simple_unload b/simple/simple_unload new file mode 100644 index 0000000..dca9421 --- /dev/null +++ b/simple/simple_unload @@ -0,0 +1,14 @@ +#!/bin/sh +module="simple" +device="simple" + +# invoke rmmod with all arguments we got +/sbin/rmmod $module $* || exit 1 + +# Remove stale nodes +rm -f /dev/${device}[rn] + + + + + diff --git a/skull/Makefile b/skull/Makefile new file mode 100644 index 0000000..0152a79 --- /dev/null +++ b/skull/Makefile @@ -0,0 +1 @@ +foo: diff --git a/skull/skull_clean.c b/skull/skull_clean.c new file mode 100644 index 0000000..9d1e789 --- /dev/null +++ b/skull/skull_clean.c @@ -0,0 +1,22 @@ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/version.h> + +#include <linux/ioport.h> + +void skull_release(unsigned int port, unsigned int range) +{ + release_region(port,range); +} + +void skull_cleanup(void) +{ + /* should put real values here ... */ + /* skull_release(0,0); */ +} + +module_exit(skull_cleanup); + + + + diff --git a/skull/skull_init.c b/skull/skull_init.c new file mode 100644 index 0000000..aaa06e2 --- /dev/null +++ b/skull/skull_init.c @@ -0,0 +1,200 @@ +/* + * skull.c -- sample typeless module. + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * BUGS: + * -it only runs on intel platforms. + * -readb() should be used (see short.c): skull doesn't work with 2.1 + * + */ + +/* jc: cleaned up, but not yet run for anything */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/moduleparam.h> + +#include <linux/kernel.h> /* printk */ +#include <linux/ioport.h> +#include <linux/errno.h> +#include <asm/system.h> /* cli(), *_flags */ +#include <linux/mm.h> /* vremap (2.0) */ +#include <asm/io.h> /* ioremap */ + +/* The region we look at. */ +#define ISA_REGION_BEGIN 0xA0000 +#define ISA_REGION_END 0x100000 +#define STEP 2048 + +/* have three symbols to export */ + void skull_fn1(void){} +static void skull_fn2(void){} + int skull_variable; + +EXPORT_SYMBOL (skull_fn1); +EXPORT_SYMBOL (skull_fn2); +EXPORT_SYMBOL (skull_variable); + + +/* perform hardware autodetection */ +int skull_probe_hw(unsigned int port, unsigned int range) +{ + /* do smart probing here */ + return -1; /* not found :-) */ +} + +/* perform hardware initalizazion */ +int skull_init_board(unsigned int port) +{ + /* do smart initalization here */ + return 0; /* done :-) */ +} + +/* detect the the device if the region is still free */ +static int skull_detect(unsigned int port, unsigned int range) +{ + int err; + + if ((err = check_region(port,range)) < 0) return err; /* busy */ + if (skull_probe_hw(port,range) != 0) return -ENODEV; /* not found */ + request_region(port,range,"skull"); /* "Can't fail" */ + return 0; +} + +/* + * port ranges: the device can reside between + * 0x280 and 0x300, in step of 0x10. It uses 0x10 ports. + */ +#define SKULL_PORT_FLOOR 0x280 +#define SKULL_PORT_CEIL 0x300 +#define SKULL_PORT_RANGE 0x010 + +/* + * the following function performs autodetection, unless a specific + * value was assigned by insmod to "skull_port_base" + */ + +static int skull_port_base=0; /* 0 forces autodetection */ +module_param(skull_port_base, int, 0); + +static int skull_find_hw(void) /* returns the # of devices */ +{ + /* base is either the load-time value or the first trial */ + int base = skull_port_base ? skull_port_base + : SKULL_PORT_FLOOR; + int result = 0; + + /* loop one time if value assigned, try them all if autodetecting */ + do { + if (skull_detect(base, SKULL_PORT_RANGE) == 0) { + skull_init_board(base); + result++; + } + base += SKULL_PORT_RANGE; /* prepare for next trial */ + } + while (skull_port_base == 0 && base < SKULL_PORT_CEIL); + + return result; +} + + +int skull_init(void) +{ + /* + * Print the isa region map, in blocks of 2K bytes. + * This is not the best code, as it prints too many lines, + * but it deserves to remain short to be included in the book. + * Note also that read() should be used instead of pointers. + */ + unsigned char oldval, newval; /* values read from memory */ + unsigned long flags; /* used to hold system flags */ + unsigned long add, i; + void *base; + + /* Use ioremap to get a handle on our region */ + base = ioremap(ISA_REGION_BEGIN, ISA_REGION_END - ISA_REGION_BEGIN); + base -= ISA_REGION_BEGIN; /* Do the offset once */ + + /* probe all the memory hole in 2KB steps */ + for (add = ISA_REGION_BEGIN; add < ISA_REGION_END; add += STEP) { + /* + * Check for an already allocated region. + */ + if (check_mem_region (add, 2048)) { + printk(KERN_INFO "%lx: Allocated\n", add); + continue; + } + /* + * Read and write the beginning of the region and see what happens. + */ + save_flags(flags); + cli(); + oldval = readb (base + add); /* Read a byte */ + writeb (oldval^0xff, base + add); + mb(); + newval = readb (base + add); + writeb (oldval, base + add); + restore_flags(flags); + + if ((oldval^newval) == 0xff) { /* we re-read our change: it's ram */ + printk(KERN_INFO "%lx: RAM\n", add); + continue; + } + if ((oldval^newval) != 0) { /* random bits changed: it's empty */ + printk(KERN_INFO "%lx: empty\n", add); + continue; + } + + /* + * Expansion rom (executed at boot time by the bios) + * has a signature where the first byt is 0x55, the second 0xaa, + * and the third byte indicates the size of such rom + */ + if ( (oldval == 0x55) && (readb (base + add + 1) == 0xaa)) { + int size = 512 * readb (base + add + 2); + printk(KERN_INFO "%lx: Expansion ROM, %i bytes\n", + add, size); + add += (size & ~2048) - 2048; /* skip it */ + continue; + } + + /* + * If the tests above failed, we still don't know if it is ROM or + * empty. Since empty memory can appear as 0x00, 0xff, or the low + * address byte, we must probe multiple bytes: if at least one of + * them is different from these three values, then this is rom + * (though not boot rom). + */ + printk(KERN_INFO "%lx: ", add); + for (i=0; i<5; i++) { + unsigned long radd = add + 57*(i+1); /* a "random" value */ + unsigned char val = readb (base + radd); + if (val && val != 0xFF && val != ((unsigned long) radd&0xFF)) + break; + } + printk("%s\n", i==5 ? "empty" : "ROM"); + } + + /* + * Find you hardware + */ + skull_find_hw(); + + /* + * Always fail to load (or suceed). + */ + return 0; +} + +module_init(skull_init); diff --git a/snull/Makefile b/snull/Makefile new file mode 100644 index 0000000..17949c1 --- /dev/null +++ b/snull/Makefile @@ -0,0 +1,41 @@ +# Comment/uncomment the following line to disable/enable debugging +#DEBUG = y + + +# Add your debugging flag (or not) to CFLAGS +ifeq ($(DEBUG),y) + DEBFLAGS = -O -g -DSBULL_DEBUG # "-O" is needed to expand inlines +else + DEBFLAGS = -O2 +endif + +CFLAGS += $(DEBFLAGS) +CFLAGS += -I.. + +ifneq ($(KERNELRELEASE),) +# call from kernel build system + +obj-m := snull.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +default: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +endif + + + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/snull/SNULLO~1.CMD b/snull/SNULLO~1.CMD new file mode 100755 index 0000000..f5d9303 --- /dev/null +++ b/snull/SNULLO~1.CMD @@ -0,0 +1,512 @@ +cmd_/media/hda3/desarrollo/kernel/examples/snull/snull.o := gcc -m32 -Wp,-MD,/media/hda3/desarrollo/kernel/examples/snull/.snull.o.d -nostdinc -isystem /usr/lib/gcc/i486-linux-gnu/4.1.2/include -D__KERNEL__ -Iinclude -include include/linux/autoconf.h -Iubuntu/include -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -O2 -pipe -msoft-float -mregparm=3 -mpreferred-stack-boundary=2 -march=i586 -mtune=generic -ffreestanding -maccumulate-outgoing-args -Iinclude/asm-i386/mach-default -fomit-frame-pointer -g -fno-stack-protector -Wdeclaration-after-statement -Wno-pointer-sign -O2 -I.. -DMODULE -D"KBUILD_STR(s)=\#s" -D"KBUILD_BASENAME=KBUILD_STR(snull)" -D"KBUILD_MODNAME=KBUILD_STR(snull)" -c -o /media/hda3/desarrollo/kernel/examples/snull/.tmp_snull.o /media/hda3/desarrollo/kernel/examples/snull/snull.c + +deps_/media/hda3/desarrollo/kernel/examples/snull/snull.o := \ + /media/hda3/desarrollo/kernel/examples/snull/snull.c \ + include/linux/module.h \ + $(wildcard include/config/modules.h) \ + $(wildcard include/config/modversions.h) \ + $(wildcard include/config/unused/symbols.h) \ + $(wildcard include/config/generic/bug.h) \ + $(wildcard include/config/module/unload.h) \ + $(wildcard include/config/kallsyms.h) \ + include/linux/spinlock.h \ + $(wildcard include/config/smp.h) \ + $(wildcard include/config/debug/spinlock.h) \ + $(wildcard include/config/preempt.h) \ + $(wildcard include/config/debug/lock/alloc.h) \ + include/linux/preempt.h \ + $(wildcard include/config/debug/preempt.h) \ + include/linux/thread_info.h \ + include/linux/bitops.h \ + include/asm/types.h \ + $(wildcard include/config/highmem64g.h) \ + include/asm/bitops.h \ + include/linux/compiler.h \ + $(wildcard include/config/enable/must/check.h) \ + include/linux/compiler-gcc4.h \ + $(wildcard include/config/forced/inlining.h) \ + include/linux/compiler-gcc.h \ + include/asm/alternative.h \ + $(wildcard include/config/paravirt.h) \ + include/linux/stddef.h \ + include/linux/types.h \ + $(wildcard include/config/uid16.h) \ + $(wildcard include/config/lbd.h) \ + $(wildcard include/config/lsf.h) \ + $(wildcard include/config/resources/64bit.h) \ + include/linux/posix_types.h \ + include/asm/posix_types.h \ + include/asm-generic/bitops/sched.h \ + include/asm-generic/bitops/hweight.h \ + include/asm-generic/bitops/fls64.h \ + include/asm-generic/bitops/ext2-non-atomic.h \ + include/asm-generic/bitops/le.h \ + include/asm/byteorder.h \ + $(wildcard include/config/x86/bswap.h) \ + include/linux/byteorder/little_endian.h \ + include/linux/byteorder/swab.h \ + include/linux/byteorder/generic.h \ + include/asm-generic/bitops/minix.h \ + include/asm/thread_info.h \ + $(wildcard include/config/4kstacks.h) \ + $(wildcard include/config/debug/stack/usage.h) \ + include/asm/page.h \ + $(wildcard include/config/x86/use/3dnow.h) \ + $(wildcard include/config/x86/pae.h) \ + $(wildcard include/config/hugetlb/page.h) \ + $(wildcard include/config/highmem4g.h) \ + $(wildcard include/config/page/offset.h) \ + $(wildcard include/config/flatmem.h) \ + $(wildcard include/config/compat/vdso.h) \ + include/asm-generic/pgtable-nopmd.h \ + include/asm-generic/pgtable-nopud.h \ + include/asm-generic/memory_model.h \ + $(wildcard include/config/discontigmem.h) \ + $(wildcard include/config/sparsemem.h) \ + $(wildcard include/config/out/of/line/pfn/to/page.h) \ + include/asm-generic/page.h \ + include/asm/processor.h \ + $(wildcard include/config/x86/ht.h) \ + $(wildcard include/config/mk8.h) \ + $(wildcard include/config/mk7.h) \ + include/asm/vm86.h \ + $(wildcard include/config/vm86.h) \ + include/asm/ptrace.h \ + include/asm/ptrace-abi.h \ + include/asm/segment.h \ + include/asm/math_emu.h \ + include/asm/sigcontext.h \ + include/asm/cpufeature.h \ + include/asm/msr.h \ + include/asm/paravirt.h \ + $(wildcard include/config/x86/local/apic.h) \ + include/linux/linkage.h \ + include/asm/linkage.h \ + $(wildcard include/config/x86/alignment/16.h) \ + include/linux/stringify.h \ + include/asm/system.h \ + $(wildcard include/config/x86/cmpxchg64.h) \ + $(wildcard include/config/x86/cmpxchg.h) \ + $(wildcard include/config/x86/oostore.h) \ + include/linux/kernel.h \ + $(wildcard include/config/preempt/voluntary.h) \ + $(wildcard include/config/debug/spinlock/sleep.h) \ + $(wildcard include/config/printk.h) \ + $(wildcard include/config/numa.h) \ + /usr/lib/gcc/i486-linux-gnu/4.1.2/include/stdarg.h \ + include/linux/log2.h \ + $(wildcard include/config/arch/has/ilog2/u32.h) \ + $(wildcard include/config/arch/has/ilog2/u64.h) \ + include/asm/bug.h \ + $(wildcard include/config/bug.h) \ + $(wildcard include/config/debug/bugverbose.h) \ + include/asm-generic/bug.h \ + include/linux/irqflags.h \ + $(wildcard include/config/trace/irqflags.h) \ + $(wildcard include/config/trace/irqflags/support.h) \ + $(wildcard include/config/x86.h) \ + include/asm/irqflags.h \ + include/linux/cache.h \ + include/asm/cache.h \ + $(wildcard include/config/x86/l1/cache/shift.h) \ + include/linux/threads.h \ + $(wildcard include/config/nr/cpus.h) \ + $(wildcard include/config/base/small.h) \ + include/asm/percpu.h \ + include/asm-generic/percpu.h \ + include/linux/cpumask.h \ + $(wildcard include/config/hotplug/cpu.h) \ + include/linux/bitmap.h \ + include/linux/string.h \ + include/asm/string.h \ + include/linux/init.h \ + $(wildcard include/config/hotplug.h) \ + $(wildcard include/config/memory/hotplug.h) \ + $(wildcard include/config/acpi/hotplug/memory.h) \ + $(wildcard include/config/acpi/hotplug/memory/module.h) \ + include/linux/bottom_half.h \ + include/linux/spinlock_types.h \ + include/linux/lockdep.h \ + $(wildcard include/config/lockdep.h) \ + $(wildcard include/config/generic/hardirqs.h) \ + $(wildcard include/config/prove/locking.h) \ + include/asm/spinlock_types.h \ + include/asm/spinlock.h \ + $(wildcard include/config/x86/ppro/fence.h) \ + include/asm/atomic.h \ + $(wildcard include/config/m386.h) \ + include/asm-generic/atomic.h \ + include/asm/rwlock.h \ + include/linux/spinlock_api_smp.h \ + include/linux/list.h \ + $(wildcard include/config/debug/list.h) \ + include/linux/poison.h \ + include/linux/prefetch.h \ + include/linux/stat.h \ + include/asm/stat.h \ + include/linux/time.h \ + include/linux/seqlock.h \ + include/linux/kmod.h \ + $(wildcard include/config/kmod.h) \ + include/linux/errno.h \ + include/asm/errno.h \ + include/asm-generic/errno.h \ + include/asm-generic/errno-base.h \ + include/linux/elf.h \ + include/linux/auxvec.h \ + include/asm/auxvec.h \ + include/linux/elf-em.h \ + include/asm/elf.h \ + include/asm/user.h \ + include/linux/utsname.h \ + $(wildcard include/config/uts/ns.h) \ + include/linux/sched.h \ + $(wildcard include/config/detect/softlockup.h) \ + $(wildcard include/config/split/ptlock/cpus.h) \ + $(wildcard include/config/keys.h) \ + $(wildcard include/config/bsd/process/acct.h) \ + $(wildcard include/config/taskstats.h) \ + $(wildcard include/config/inotify/user.h) \ + $(wildcard include/config/schedstats.h) \ + $(wildcard include/config/task/delay/acct.h) \ + $(wildcard include/config/blk/dev/io/trace.h) \ + $(wildcard include/config/cc/stackprotector.h) \ + $(wildcard include/config/sysvipc.h) \ + $(wildcard include/config/rt/mutexes.h) \ + $(wildcard include/config/debug/mutexes.h) \ + $(wildcard include/config/task/xacct.h) \ + $(wildcard include/config/cpusets.h) \ + $(wildcard include/config/compat.h) \ + $(wildcard include/config/fault/injection.h) \ + include/asm/param.h \ + $(wildcard include/config/hz.h) \ + include/linux/capability.h \ + include/asm/current.h \ + include/asm/pda.h \ + include/linux/timex.h \ + $(wildcard include/config/time/interpolation.h) \ + include/asm/timex.h \ + $(wildcard include/config/x86/elan.h) \ + include/asm/tsc.h \ + $(wildcard include/config/x86/tsc.h) \ + $(wildcard include/config/x86/generic.h) \ + include/linux/jiffies.h \ + include/linux/calc64.h \ + include/asm/div64.h \ + include/linux/rbtree.h \ + include/linux/nodemask.h \ + include/linux/numa.h \ + $(wildcard include/config/nodes/shift.h) \ + include/asm/semaphore.h \ + include/linux/wait.h \ + include/linux/rwsem.h \ + $(wildcard include/config/rwsem/generic/spinlock.h) \ + include/asm/rwsem.h \ + include/asm/mmu.h \ + include/asm/cputime.h \ + include/asm-generic/cputime.h \ + include/linux/smp.h \ + include/asm/smp.h \ + $(wildcard include/config/x86/io/apic.h) \ + include/asm/fixmap.h \ + $(wildcard include/config/highmem.h) \ + $(wildcard include/config/x86/visws/apic.h) \ + $(wildcard include/config/x86/f00f/bug.h) \ + $(wildcard include/config/x86/cyclone/timer.h) \ + $(wildcard include/config/acpi.h) \ + $(wildcard include/config/pci/mmconfig.h) \ + include/asm/acpi.h \ + $(wildcard include/config/acpi/sleep.h) \ + include/acpi/pdc_intel.h \ + include/asm/apicdef.h \ + include/asm/kmap_types.h \ + $(wildcard include/config/debug/highmem.h) \ + include/asm/mpspec.h \ + include/asm/mpspec_def.h \ + include/asm-i386/mach-default/mach_mpspec.h \ + include/asm/io_apic.h \ + include/asm/apic.h \ + $(wildcard include/config/x86/good/apic.h) \ + include/linux/pm.h \ + $(wildcard include/config/pm.h) \ + include/asm-i386/mach-default/mach_apicdef.h \ + include/linux/sem.h \ + include/linux/ipc.h \ + $(wildcard include/config/ipc/ns.h) \ + include/asm/ipcbuf.h \ + include/linux/kref.h \ + include/asm/sembuf.h \ + include/linux/signal.h \ + include/asm/signal.h \ + include/asm-generic/signal.h \ + include/asm/siginfo.h \ + include/asm-generic/siginfo.h \ + include/linux/securebits.h \ + include/linux/fs_struct.h \ + include/linux/completion.h \ + include/linux/pid.h \ + include/linux/rcupdate.h \ + include/linux/percpu.h \ + include/linux/slab.h \ + $(wildcard include/config/slab/debug.h) \ + $(wildcard include/config/slab.h) \ + $(wildcard include/config/debug/slab.h) \ + include/linux/gfp.h \ + $(wildcard include/config/zone/dma32.h) \ + include/linux/mmzone.h \ + $(wildcard include/config/force/max/zoneorder.h) \ + $(wildcard include/config/arch/populates/node/map.h) \ + $(wildcard include/config/flat/node/mem/map.h) \ + $(wildcard include/config/have/memory/present.h) \ + $(wildcard include/config/need/node/memmap/size.h) \ + $(wildcard include/config/need/multiple/nodes.h) \ + $(wildcard include/config/have/arch/early/pfn/to/nid.h) \ + $(wildcard include/config/sparsemem/extreme.h) \ + $(wildcard include/config/nodes/span/other/nodes.h) \ + include/linux/memory_hotplug.h \ + $(wildcard include/config/have/arch/nodedata/extension.h) \ + include/linux/notifier.h \ + include/linux/mutex.h \ + include/linux/srcu.h \ + include/linux/topology.h \ + $(wildcard include/config/sched/smt.h) \ + $(wildcard include/config/sched/mc.h) \ + include/asm/topology.h \ + include/asm-generic/topology.h \ + include/linux/slab_def.h \ + include/linux/kmalloc_sizes.h \ + $(wildcard include/config/mmu.h) \ + $(wildcard include/config/large/allocs.h) \ + include/linux/seccomp.h \ + $(wildcard include/config/seccomp.h) \ + include/asm/seccomp.h \ + include/linux/unistd.h \ + include/asm/unistd.h \ + include/linux/futex.h \ + $(wildcard include/config/futex.h) \ + include/linux/rtmutex.h \ + $(wildcard include/config/debug/rt/mutexes.h) \ + include/linux/plist.h \ + $(wildcard include/config/debug/pi/list.h) \ + include/linux/param.h \ + include/linux/resource.h \ + include/asm/resource.h \ + include/asm-generic/resource.h \ + include/linux/timer.h \ + include/linux/hrtimer.h \ + $(wildcard include/config/no/idle/hz.h) \ + include/linux/ktime.h \ + $(wildcard include/config/ktime/scalar.h) \ + include/linux/task_io_accounting.h \ + $(wildcard include/config/task/io/accounting.h) \ + include/linux/aio.h \ + include/linux/workqueue.h \ + include/linux/aio_abi.h \ + include/linux/uio.h \ + include/linux/sysdev.h \ + include/linux/kobject.h \ + include/linux/sysfs.h \ + $(wildcard include/config/sysfs.h) \ + include/linux/nsproxy.h \ + include/asm/desc.h \ + include/asm/ldt.h \ + include/linux/moduleparam.h \ + include/asm/local.h \ + include/asm/module.h \ + $(wildcard include/config/m486.h) \ + $(wildcard include/config/m586.h) \ + $(wildcard include/config/m586tsc.h) \ + $(wildcard include/config/m586mmx.h) \ + $(wildcard include/config/mcore2.h) \ + $(wildcard include/config/m686.h) \ + $(wildcard include/config/mpentiumii.h) \ + $(wildcard include/config/mpentiumiii.h) \ + $(wildcard include/config/mpentiumm.h) \ + $(wildcard include/config/mpentium4.h) \ + $(wildcard include/config/mk6.h) \ + $(wildcard include/config/mcrusoe.h) \ + $(wildcard include/config/mefficeon.h) \ + $(wildcard include/config/mwinchipc6.h) \ + $(wildcard include/config/mwinchip2.h) \ + $(wildcard include/config/mwinchip3d.h) \ + $(wildcard include/config/mcyrixiii.h) \ + $(wildcard include/config/mviac3/2.h) \ + $(wildcard include/config/mgeodegx1.h) \ + $(wildcard include/config/mgeode/lx.h) \ + include/linux/interrupt.h \ + $(wildcard include/config/generic/irq/probe.h) \ + include/linux/irqreturn.h \ + include/linux/hardirq.h \ + $(wildcard include/config/preempt/bkl.h) \ + $(wildcard include/config/virt/cpu/accounting.h) \ + include/linux/smp_lock.h \ + $(wildcard include/config/lock/kernel.h) \ + include/asm/hardirq.h \ + include/linux/irq.h \ + $(wildcard include/config/s390.h) \ + $(wildcard include/config/irq/per/cpu.h) \ + $(wildcard include/config/irq/release/method.h) \ + $(wildcard include/config/generic/pending/irq.h) \ + $(wildcard include/config/irqbalance.h) \ + $(wildcard include/config/proc/fs.h) \ + $(wildcard include/config/auto/irq/affinity.h) \ + $(wildcard include/config/generic/hardirqs/no//do/irq.h) \ + include/asm/irq.h \ + include/asm-i386/mach-default/irq_vectors.h \ + include/asm-i386/mach-default/irq_vectors_limits.h \ + include/asm/irq_regs.h \ + include/asm/hw_irq.h \ + include/linux/profile.h \ + $(wildcard include/config/profiling.h) \ + include/asm/sections.h \ + include/asm-generic/sections.h \ + include/linux/irq_cpustat.h \ + include/linux/device.h \ + $(wildcard include/config/debug/devres.h) \ + include/linux/ioport.h \ + include/linux/klist.h \ + include/asm/device.h \ + include/linux/in.h \ + include/linux/socket.h \ + include/asm/socket.h \ + include/asm/sockios.h \ + include/linux/sockios.h \ + include/linux/netdevice.h \ + $(wildcard include/config/ax25.h) \ + $(wildcard include/config/ax25/module.h) \ + $(wildcard include/config/tr.h) \ + $(wildcard include/config/net/ipip.h) \ + $(wildcard include/config/net/ipip/module.h) \ + $(wildcard include/config/net/ipgre.h) \ + $(wildcard include/config/net/ipgre/module.h) \ + $(wildcard include/config/ipv6/sit.h) \ + $(wildcard include/config/ipv6/sit/module.h) \ + $(wildcard include/config/ipv6/tunnel.h) \ + $(wildcard include/config/ipv6/tunnel/module.h) \ + $(wildcard include/config/netpoll.h) \ + $(wildcard include/config/net/poll/controller.h) \ + $(wildcard include/config/netpoll/trap.h) \ + $(wildcard include/config/net/dma.h) \ + include/linux/if.h \ + include/linux/hdlc/ioctl.h \ + include/linux/if_ether.h \ + $(wildcard include/config/sysctl.h) \ + include/linux/skbuff.h \ + $(wildcard include/config/netfilter.h) \ + $(wildcard include/config/bridge/netfilter.h) \ + $(wildcard include/config/vlan/8021q.h) \ + $(wildcard include/config/vlan/8021q/module.h) \ + $(wildcard include/config/nf/conntrack.h) \ + $(wildcard include/config/nf/conntrack/module.h) \ + $(wildcard include/config/net/sched.h) \ + $(wildcard include/config/net/cls/act.h) \ + $(wildcard include/config/network/secmark.h) \ + include/linux/net.h \ + include/linux/random.h \ + include/linux/ioctl.h \ + include/asm/ioctl.h \ + include/asm-generic/ioctl.h \ + include/linux/sysctl.h \ + include/linux/textsearch.h \ + include/linux/err.h \ + include/net/checksum.h \ + include/asm/uaccess.h \ + $(wildcard include/config/x86/intel/usercopy.h) \ + $(wildcard include/config/x86/wp/works/ok.h) \ + include/asm/checksum.h \ + include/linux/in6.h \ + include/linux/dmaengine.h \ + $(wildcard include/config/dma/engine.h) \ + include/linux/if_packet.h \ + include/linux/etherdevice.h \ + include/linux/ip.h \ + include/linux/tcp.h \ + $(wildcard include/config/tcp/md5sig.h) \ + include/net/sock.h \ + $(wildcard include/config/security/network.h) \ + $(wildcard include/config/netdebug.h) \ + $(wildcard include/config/net.h) \ + include/linux/mm.h \ + $(wildcard include/config/stack/growsup.h) \ + $(wildcard include/config/debug/vm.h) \ + $(wildcard include/config/shmem.h) \ + $(wildcard include/config/ia64.h) \ + $(wildcard include/config/debug/pagealloc.h) \ + include/linux/prio_tree.h \ + include/linux/fs.h \ + $(wildcard include/config/dnotify.h) \ + $(wildcard include/config/quota.h) \ + $(wildcard include/config/inotify.h) \ + $(wildcard include/config/security.h) \ + $(wildcard include/config/epoll.h) \ + $(wildcard include/config/auditsyscall.h) \ + $(wildcard include/config/block.h) \ + $(wildcard include/config/fs/xip.h) \ + $(wildcard include/config/migration.h) \ + include/linux/limits.h \ + include/linux/kdev_t.h \ + include/linux/dcache.h \ + include/linux/namei.h \ + include/linux/radix-tree.h \ + include/linux/quota.h \ + include/linux/dqblk_xfs.h \ + include/linux/dqblk_v1.h \ + include/linux/dqblk_v2.h \ + include/linux/nfs_fs_i.h \ + include/linux/nfs.h \ + include/linux/sunrpc/msg_prot.h \ + include/linux/fcntl.h \ + include/asm/fcntl.h \ + include/asm-generic/fcntl.h \ + $(wildcard include/config/64bit.h) \ + include/linux/debug_locks.h \ + $(wildcard include/config/debug/locking/api/selftests.h) \ + include/linux/backing-dev.h \ + include/linux/mm_types.h \ + include/asm/pgtable.h \ + $(wildcard include/config/highpte.h) \ + include/asm/pgtable-2level-defs.h \ + include/asm/pgtable-2level.h \ + include/asm-generic/pgtable.h \ + include/linux/page-flags.h \ + $(wildcard include/config/swap.h) \ + include/linux/vmstat.h \ + $(wildcard include/config/vm/event/counters.h) \ + include/linux/security.h \ + $(wildcard include/config/security/network/xfrm.h) \ + include/linux/binfmts.h \ + include/linux/shm.h \ + include/asm/shmparam.h \ + include/asm/shmbuf.h \ + include/linux/msg.h \ + include/asm/msgbuf.h \ + include/linux/key.h \ + include/linux/xfrm.h \ + include/net/flow.h \ + $(wildcard include/config/ipv6/mip6.h) \ + include/linux/filter.h \ + include/net/dst.h \ + $(wildcard include/config/net/cls/route.h) \ + $(wildcard include/config/xfrm.h) \ + include/linux/rtnetlink.h \ + include/linux/netlink.h \ + include/linux/if_link.h \ + include/linux/if_addr.h \ + include/linux/neighbour.h \ + include/net/neighbour.h \ + include/linux/seq_file.h \ + include/net/inet_connection_sock.h \ + include/linux/poll.h \ + include/asm/poll.h \ + include/net/inet_sock.h \ + $(wildcard include/config/ipv6.h) \ + $(wildcard include/config/ipv6/module.h) \ + include/net/request_sock.h \ + include/net/inet_timewait_sock.h \ + include/net/tcp_states.h \ + include/net/timewait_sock.h \ + /media/hda3/desarrollo/kernel/examples/snull/snull.h \ + +/media/hda3/desarrollo/kernel/examples/snull/snull.o: $(deps_/media/hda3/desarrollo/kernel/examples/snull/snull.o) + +$(deps_/media/hda3/desarrollo/kernel/examples/snull/snull.o): diff --git a/snull/snull.c b/snull/snull.c new file mode 100644 index 0000000..6fbde81 --- /dev/null +++ b/snull/snull.c @@ -0,0 +1,736 @@ +/* + * snull.c -- the Simple Network Utility + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + * $Id: snull.c,v 1.21 2004/11/05 02:36:03 rubini Exp $ + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/moduleparam.h> + +#include <linux/sched.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/slab.h> /* kmalloc() */ +#include <linux/errno.h> /* error codes */ +#include <linux/types.h> /* size_t */ +#include <linux/interrupt.h> /* mark_bh */ + +#include <linux/in.h> +#include <linux/netdevice.h> /* struct device, and other headers */ +#include <linux/etherdevice.h> /* eth_type_trans */ +#include <linux/ip.h> /* struct iphdr */ +#include <linux/tcp.h> /* struct tcphdr */ +#include <linux/skbuff.h> + +#include "snull.h" + +#include <linux/in6.h> +#include <asm/checksum.h> + +MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet"); +MODULE_LICENSE("Dual BSD/GPL"); + + +/* + * Transmitter lockup simulation, normally disabled. + */ +static int lockup = 0; +module_param(lockup, int, 0); + +static int timeout = SNULL_TIMEOUT; +module_param(timeout, int, 0); + +/* + * Do we run in NAPI mode? + */ +static int use_napi = 0; +module_param(use_napi, int, 0); + + +/* + * A structure representing an in-flight packet. + */ +struct snull_packet { + struct snull_packet *next; + struct net_device *dev; + int datalen; + u8 data[ETH_DATA_LEN]; +}; + +int pool_size = 8; +module_param(pool_size, int, 0); + +/* + * This structure is private to each device. It is used to pass + * packets in and out, so there is place for a packet + */ + +struct snull_priv { + struct net_device_stats stats; + int status; + struct snull_packet *ppool; + struct snull_packet *rx_queue; /* List of incoming packets */ + int rx_int_enabled; + int tx_packetlen; + u8 *tx_packetdata; + struct sk_buff *skb; + spinlock_t lock; +}; + +static void snull_tx_timeout(struct net_device *dev); +static void (*snull_interrupt)(int, void *, struct pt_regs *); + +/* + * Set up a device's packet pool. + */ +void snull_setup_pool(struct net_device *dev) +{ + struct snull_priv *priv = netdev_priv(dev); + int i; + struct snull_packet *pkt; + + priv->ppool = NULL; + for (i = 0; i < pool_size; i++) { + pkt = kmalloc (sizeof (struct snull_packet), GFP_KERNEL); + if (pkt == NULL) { + printk (KERN_NOTICE "Ran out of memory allocating packet pool\n"); + return; + } + pkt->dev = dev; + pkt->next = priv->ppool; + priv->ppool = pkt; + } +} + +void snull_teardown_pool(struct net_device *dev) +{ + struct snull_priv *priv = netdev_priv(dev); + struct snull_packet *pkt; + + while ((pkt = priv->ppool)) { + priv->ppool = pkt->next; + kfree (pkt); + /* FIXME - in-flight packets ? */ + } +} + +/* + * Buffer/pool management. + */ +struct snull_packet *snull_get_tx_buffer(struct net_device *dev) +{ + struct snull_priv *priv = netdev_priv(dev); + unsigned long flags; + struct snull_packet *pkt; + + spin_lock_irqsave(&priv->lock, flags); + pkt = priv->ppool; + priv->ppool = pkt->next; + if (priv->ppool == NULL) { + printk (KERN_INFO "Pool empty\n"); + netif_stop_queue(dev); + } + spin_unlock_irqrestore(&priv->lock, flags); + return pkt; +} + + +void snull_release_buffer(struct snull_packet *pkt) +{ + unsigned long flags; + struct snull_priv *priv = netdev_priv(pkt->dev); + + spin_lock_irqsave(&priv->lock, flags); + pkt->next = priv->ppool; + priv->ppool = pkt; + spin_unlock_irqrestore(&priv->lock, flags); + if (netif_queue_stopped(pkt->dev) && pkt->next == NULL) + netif_wake_queue(pkt->dev); +} + +void snull_enqueue_buf(struct net_device *dev, struct snull_packet *pkt) +{ + unsigned long flags; + struct snull_priv *priv = netdev_priv(dev); + + spin_lock_irqsave(&priv->lock, flags); + pkt->next = priv->rx_queue; /* FIXME - misorders packets */ + priv->rx_queue = pkt; + spin_unlock_irqrestore(&priv->lock, flags); +} + +struct snull_packet *snull_dequeue_buf(struct net_device *dev) +{ + struct snull_priv *priv = netdev_priv(dev); + struct snull_packet *pkt; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + pkt = priv->rx_queue; + if (pkt != NULL) + priv->rx_queue = pkt->next; + spin_unlock_irqrestore(&priv->lock, flags); + return pkt; +} + +/* + * Enable and disable receive interrupts. + */ +static void snull_rx_ints(struct net_device *dev, int enable) +{ + struct snull_priv *priv = netdev_priv(dev); + priv->rx_int_enabled = enable; +} + + +/* + * Open and close + */ + +int snull_open(struct net_device *dev) +{ + /* request_region(), request_irq(), .... (like fops->open) */ + + /* + * Assign the hardware address of the board: use "\0SNULx", where + * x is 0 or 1. The first byte is '\0' to avoid being a multicast + * address (the first byte of multicast addrs is odd). + */ + memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN); + if (dev == snull_devs[1]) + dev->dev_addr[ETH_ALEN-1]++; /* \0SNUL1 */ + netif_start_queue(dev); + return 0; +} + +int snull_release(struct net_device *dev) +{ + /* release ports, irq and such -- like fops->close */ + + netif_stop_queue(dev); /* can't transmit any more */ + return 0; +} + +/* + * Configuration changes (passed on by ifconfig) + */ +int snull_config(struct net_device *dev, struct ifmap *map) +{ + if (dev->flags & IFF_UP) /* can't act on a running interface */ + return -EBUSY; + + /* Don't allow changing the I/O address */ + if (map->base_addr != dev->base_addr) { + printk(KERN_WARNING "snull: Can't change I/O address\n"); + return -EOPNOTSUPP; + } + + /* Allow changing the IRQ */ + if (map->irq != dev->irq) { + dev->irq = map->irq; + /* request_irq() is delayed to open-time */ + } + + /* ignore other fields */ + return 0; +} + +/* + * Receive a packet: retrieve, encapsulate and pass over to upper levels + */ +void snull_rx(struct net_device *dev, struct snull_packet *pkt) +{ + struct sk_buff *skb; + struct snull_priv *priv = netdev_priv(dev); + + /* + * The packet has been retrieved from the transmission + * medium. Build an skb around it, so upper layers can handle it + */ + skb = dev_alloc_skb(pkt->datalen + 2); + if (!skb) { + if (printk_ratelimit()) + printk(KERN_NOTICE "snull rx: low on mem - packet dropped\n"); + priv->stats.rx_dropped++; + goto out; + } + skb_reserve(skb, 2); /* align IP on 16B boundary */ + memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen); + + /* Write metadata, and then pass to the receive level */ + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */ + priv->stats.rx_packets++; + priv->stats.rx_bytes += pkt->datalen; + netif_rx(skb); + out: + return; +} + + +/* + * The poll implementation. + */ +static int snull_poll(struct net_device *dev, int *budget) +{ + int npackets = 0, quota = min(dev->quota, *budget); + struct sk_buff *skb; + struct snull_priv *priv = netdev_priv(dev); + struct snull_packet *pkt; + + while (npackets < quota && priv->rx_queue) { + pkt = snull_dequeue_buf(dev); + skb = dev_alloc_skb(pkt->datalen + 2); + if (! skb) { + if (printk_ratelimit()) + printk(KERN_NOTICE "snull: packet dropped\n"); + priv->stats.rx_dropped++; + snull_release_buffer(pkt); + continue; + } + skb_reserve(skb, 2); /* align IP on 16B boundary */ + memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen); + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */ + netif_receive_skb(skb); + + /* Maintain stats */ + npackets++; + priv->stats.rx_packets++; + priv->stats.rx_bytes += pkt->datalen; + snull_release_buffer(pkt); + } + /* If we processed all packets, we're done; tell the kernel and reenable ints */ + *budget -= npackets; + dev->quota -= npackets; + if (! priv->rx_queue) { + netif_rx_complete(dev); + snull_rx_ints(dev, 1); + return 0; + } + /* We couldn't process everything. */ + return 1; +} + + +/* + * The typical interrupt entry point + */ +static void snull_regular_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + int statusword; + struct snull_priv *priv; + struct snull_packet *pkt = NULL; + /* + * As usual, check the "device" pointer to be sure it is + * really interrupting. + * Then assign "struct device *dev" + */ + struct net_device *dev = (struct net_device *)dev_id; + /* ... and check with hw if it's really ours */ + + /* paranoid */ + if (!dev) + return; + + /* Lock the device */ + priv = netdev_priv(dev); + spin_lock(&priv->lock); + + /* retrieve statusword: real netdevices use I/O instructions */ + statusword = priv->status; + priv->status = 0; + if (statusword & SNULL_RX_INTR) { + /* send it to snull_rx for handling */ + pkt = priv->rx_queue; + if (pkt) { + priv->rx_queue = pkt->next; + snull_rx(dev, pkt); + } + } + if (statusword & SNULL_TX_INTR) { + /* a transmission is over: free the skb */ + priv->stats.tx_packets++; + priv->stats.tx_bytes += priv->tx_packetlen; + dev_kfree_skb(priv->skb); + } + + /* Unlock the device and we are done */ + spin_unlock(&priv->lock); + if (pkt) snull_release_buffer(pkt); /* Do this outside the lock! */ + return; +} + +/* + * A NAPI interrupt handler. + */ +static void snull_napi_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + int statusword; + struct snull_priv *priv; + + /* + * As usual, check the "device" pointer for shared handlers. + * Then assign "struct device *dev" + */ + struct net_device *dev = (struct net_device *)dev_id; + /* ... and check with hw if it's really ours */ + + /* paranoid */ + if (!dev) + return; + + /* Lock the device */ + priv = netdev_priv(dev); + spin_lock(&priv->lock); + + /* retrieve statusword: real netdevices use I/O instructions */ + statusword = priv->status; + priv->status = 0; + if (statusword & SNULL_RX_INTR) { + snull_rx_ints(dev, 0); /* Disable further interrupts */ + netif_rx_schedule(dev); + } + if (statusword & SNULL_TX_INTR) { + /* a transmission is over: free the skb */ + priv->stats.tx_packets++; + priv->stats.tx_bytes += priv->tx_packetlen; + dev_kfree_skb(priv->skb); + } + + /* Unlock the device and we are done */ + spin_unlock(&priv->lock); + return; +} + + + +/* + * Transmit a packet (low level interface) + */ +static void snull_hw_tx(char *buf, int len, struct net_device *dev) +{ + /* + * This function deals with hw details. This interface loops + * back the packet to the other snull interface (if any). + * In other words, this function implements the snull behaviour, + * while all other procedures are rather device-independent + */ + struct iphdr *ih; + struct net_device *dest; + struct snull_priv *priv; + u32 *saddr, *daddr; + struct snull_packet *tx_buffer; + + /* I am paranoid. Ain't I? */ + if (len < sizeof(struct ethhdr) + sizeof(struct iphdr)) { + printk("snull: Hmm... packet too short (%i octets)\n", + len); + return; + } + + if (0) { /* enable this conditional to look at the data */ + int i; + PDEBUG("len is %i\n" KERN_DEBUG "data:",len); + for (i=14 ; i<len; i++) + printk(" %02x",buf[i]&0xff); + printk("\n"); + } + /* + * Ethhdr is 14 bytes, but the kernel arranges for iphdr + * to be aligned (i.e., ethhdr is unaligned) + */ + ih = (struct iphdr *)(buf+sizeof(struct ethhdr)); + saddr = &ih->saddr; + daddr = &ih->daddr; + + ((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */ + ((u8 *)daddr)[2] ^= 1; + + ih->check = 0; /* and rebuild the checksum (ip needs it) */ + ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl); + + if (dev == snull_devs[0]) + PDEBUGG("%08x:%05i --> %08x:%05i\n", + ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source), + ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest)); + else + PDEBUGG("%08x:%05i <-- %08x:%05i\n", + ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest), + ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source)); + + /* + * Ok, now the packet is ready for transmission: first simulate a + * receive interrupt on the twin device, then a + * transmission-done on the transmitting device + */ + dest = snull_devs[dev == snull_devs[0] ? 1 : 0]; + priv = netdev_priv(dest); + tx_buffer = snull_get_tx_buffer(dev); + tx_buffer->datalen = len; + memcpy(tx_buffer->data, buf, len); + snull_enqueue_buf(dest, tx_buffer); + if (priv->rx_int_enabled) { + priv->status |= SNULL_RX_INTR; + snull_interrupt(0, dest, NULL); + } + + priv = netdev_priv(dev); + priv->tx_packetlen = len; + priv->tx_packetdata = buf; + priv->status |= SNULL_TX_INTR; + if (lockup && ((priv->stats.tx_packets + 1) % lockup) == 0) { + /* Simulate a dropped transmit interrupt */ + netif_stop_queue(dev); + PDEBUG("Simulate lockup at %ld, txp %ld\n", jiffies, + (unsigned long) priv->stats.tx_packets); + } + else + snull_interrupt(0, dev, NULL); +} + +/* + * Transmit a packet (called by the kernel) + */ +int snull_tx(struct sk_buff *skb, struct net_device *dev) +{ + int len; + char *data, shortpkt[ETH_ZLEN]; + struct snull_priv *priv = netdev_priv(dev); + + data = skb->data; + len = skb->len; + if (len < ETH_ZLEN) { + memset(shortpkt, 0, ETH_ZLEN); + memcpy(shortpkt, skb->data, skb->len); + len = ETH_ZLEN; + data = shortpkt; + } + dev->trans_start = jiffies; /* save the timestamp */ + + /* Remember the skb, so we can free it at interrupt time */ + priv->skb = skb; + + /* actual deliver of data is device-specific, and not shown here */ + snull_hw_tx(data, len, dev); + + return 0; /* Our simple device can not fail */ +} + +/* + * Deal with a transmit timeout. + */ +void snull_tx_timeout (struct net_device *dev) +{ + struct snull_priv *priv = netdev_priv(dev); + + PDEBUG("Transmit timeout at %ld, latency %ld\n", jiffies, + jiffies - dev->trans_start); + /* Simulate a transmission interrupt to get things moving */ + priv->status = SNULL_TX_INTR; + snull_interrupt(0, dev, NULL); + priv->stats.tx_errors++; + netif_wake_queue(dev); + return; +} + + + +/* + * Ioctl commands + */ +int snull_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + PDEBUG("ioctl\n"); + return 0; +} + +/* + * Return statistics to the caller + */ +struct net_device_stats *snull_stats(struct net_device *dev) +{ + struct snull_priv *priv = netdev_priv(dev); + return &priv->stats; +} + +/* + * This function is called to fill up an eth header, since arp is not + * available on the interface + */ +int snull_rebuild_header(struct sk_buff *skb) +{ + struct ethhdr *eth = (struct ethhdr *) skb->data; + struct net_device *dev = skb->dev; + + memcpy(eth->h_source, dev->dev_addr, dev->addr_len); + memcpy(eth->h_dest, dev->dev_addr, dev->addr_len); + eth->h_dest[ETH_ALEN-1] ^= 0x01; /* dest is us xor 1 */ + return 0; +} + + +int snull_header(struct sk_buff *skb, struct net_device *dev, + unsigned short type, void *daddr, void *saddr, + unsigned int len) +{ + struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN); + + eth->h_proto = htons(type); + memcpy(eth->h_source, saddr ? saddr : dev->dev_addr, dev->addr_len); + memcpy(eth->h_dest, daddr ? daddr : dev->dev_addr, dev->addr_len); + eth->h_dest[ETH_ALEN-1] ^= 0x01; /* dest is us xor 1 */ + return (dev->hard_header_len); +} + + + + + +/* + * The "change_mtu" method is usually not needed. + * If you need it, it must be like this. + */ +int snull_change_mtu(struct net_device *dev, int new_mtu) +{ + unsigned long flags; + struct snull_priv *priv = netdev_priv(dev); + spinlock_t *lock = &priv->lock; + + /* check ranges */ + if ((new_mtu < 68) || (new_mtu > 1500)) + return -EINVAL; + /* + * Do anything you need, and the accept the value + */ + spin_lock_irqsave(lock, flags); + dev->mtu = new_mtu; + spin_unlock_irqrestore(lock, flags); + return 0; /* success */ +} + +/* + * The init function (sometimes called probe). + * It is invoked by register_netdev() + */ +void snull_init(struct net_device *dev) +{ + struct snull_priv *priv; +#if 0 + /* + * Make the usual checks: check_region(), probe irq, ... -ENODEV + * should be returned if no device found. No resource should be + * grabbed: this is done on open(). + */ +#endif + + /* + * Then, assign other fields in dev, using ether_setup() and some + * hand assignments + */ + ether_setup(dev); /* assign some of the fields */ + + dev->open = snull_open; + dev->stop = snull_release; + dev->set_config = snull_config; + dev->hard_start_xmit = snull_tx; + dev->do_ioctl = snull_ioctl; + dev->get_stats = snull_stats; + dev->change_mtu = snull_change_mtu; + dev->rebuild_header = snull_rebuild_header; + dev->hard_header = snull_header; + dev->tx_timeout = snull_tx_timeout; + dev->watchdog_timeo = timeout; + if (use_napi) { + dev->poll = snull_poll; + dev->weight = 2; + } + /* keep the default flags, just add NOARP */ + dev->flags |= IFF_NOARP; + dev->features |= NETIF_F_NO_CSUM; + dev->hard_header_cache = NULL; /* Disable caching */ + + /* + * Then, initialize the priv field. This encloses the statistics + * and a few private fields. + */ + priv = netdev_priv(dev); + memset(priv, 0, sizeof(struct snull_priv)); + spin_lock_init(&priv->lock); + snull_rx_ints(dev, 1); /* enable receive interrupts */ + snull_setup_pool(dev); +} + +/* + * The devices + */ + +struct net_device *snull_devs[2]; + + + +/* + * Finally, the module stuff + */ + +void snull_cleanup(void) +{ + int i; + + for (i = 0; i < 2; i++) { + if (snull_devs[i]) { + unregister_netdev(snull_devs[i]); + snull_teardown_pool(snull_devs[i]); + free_netdev(snull_devs[i]); + } + } + return; +} + + + + +int snull_init_module(void) +{ + int result, i, ret = -ENOMEM; + + snull_interrupt = use_napi ? snull_napi_interrupt : snull_regular_interrupt; + + /* Allocate the devices */ + snull_devs[0] = alloc_netdev(sizeof(struct snull_priv), "sn%d", + snull_init); + snull_devs[1] = alloc_netdev(sizeof(struct snull_priv), "sn%d", + snull_init); + if (snull_devs[0] == NULL || snull_devs[1] == NULL) + goto out; + + ret = -ENODEV; + for (i = 0; i < 2; i++) + if ((result = register_netdev(snull_devs[i]))) + printk("snull: error %i registering device \"%s\"\n", + result, snull_devs[i]->name); + else + ret = 0; + out: + if (ret) + snull_cleanup(); + return ret; +} + + +module_init(snull_init_module); +module_exit(snull_cleanup); diff --git a/snull/snull.h b/snull/snull.h new file mode 100644 index 0000000..69a49a0 --- /dev/null +++ b/snull/snull.h @@ -0,0 +1,49 @@ + +/* + * snull.h -- definitions for the network module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + */ + +/* + * Macros to help debugging + */ + +#undef PDEBUG /* undef it, just in case */ +#ifdef SNULL_DEBUG +# ifdef __KERNEL__ + /* This one if debugging is on, and kernel space */ +# define PDEBUG(fmt, args...) printk( KERN_DEBUG "snull: " fmt, ## args) +# else + /* This one for user space */ +# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) +# endif +#else +# define PDEBUG(fmt, args...) /* not debugging: nothing */ +#endif + +#undef PDEBUGG +#define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */ + + +/* These are the flags in the statusword */ +#define SNULL_RX_INTR 0x0001 +#define SNULL_TX_INTR 0x0002 + +/* Default timeout period */ +#define SNULL_TIMEOUT 5 /* In jiffies */ + +extern struct net_device *snull_devs[]; + + + + diff --git a/snull/snull_load b/snull/snull_load new file mode 100644 index 0000000..47de578 --- /dev/null +++ b/snull/snull_load @@ -0,0 +1,8 @@ +#!/bin/sh + +export PATH=/sbin:/bin + +# Use a pathname, as new modutils don't look in the current dir by default +insmod ./snull.ko $* +ifconfig sn0 local0 +ifconfig sn1 local1 diff --git a/snull/snull_unload b/snull/snull_unload new file mode 100644 index 0000000..a857ac9 --- /dev/null +++ b/snull/snull_unload @@ -0,0 +1,5 @@ +#!/bin/sh + +/sbin/ifconfig sn0 down +/sbin/ifconfig sn1 down +/sbin/rmmod snull diff --git a/tty/Makefile b/tty/Makefile new file mode 100644 index 0000000..f5b8592 --- /dev/null +++ b/tty/Makefile @@ -0,0 +1,41 @@ +# Comment/uncomment the following line to disable/enable debugging +#DEBUG = y + + +# Add your debugging flag (or not) to CFLAGS +ifeq ($(DEBUG),y) + DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines +else + DEBFLAGS = -O2 +endif + +CFLAGS += $(DEBFLAGS) +CFLAGS += -I.. + +ifneq ($(KERNELRELEASE),) +# call from kernel build system + +obj-m := tiny_tty.o tiny_serial.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +default: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +endif + + + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/tty/tiny_serial.c b/tty/tiny_serial.c new file mode 100644 index 0000000..1ae7a43 --- /dev/null +++ b/tty/tiny_serial.c @@ -0,0 +1,291 @@ +/* + * Tiny Serial driver + * + * Copyright (C) 2002-2004 Greg Kroah-Hartman (greg@kroah.com) + * + * 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, version 2 of the License. + * + * This driver shows how to create a minimal serial driver. It does not rely on + * any backing hardware, but creates a timer that emulates data being received + * from some kind of hardware. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/module.h> + + +#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>" +#define DRIVER_DESC "Tiny serial driver" + +/* Module information */ +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); + +#define DELAY_TIME HZ * 2 /* 2 seconds per character */ +#define TINY_DATA_CHARACTER 't' + +#define TINY_SERIAL_MAJOR 240 /* experimental range */ +#define TINY_SERIAL_MINORS 1 /* only have one minor */ +#define UART_NR 1 /* only use one port */ + +#define TINY_SERIAL_NAME "ttytiny" + +#define MY_NAME TINY_SERIAL_NAME + +static struct timer_list *timer; + +static void tiny_stop_tx(struct uart_port *port, unsigned int tty_stop) +{ +} + +static void tiny_stop_rx(struct uart_port *port) +{ +} + +static void tiny_enable_ms(struct uart_port *port) +{ +} + +static void tiny_tx_chars(struct uart_port *port) +{ + struct circ_buf *xmit = &port->info->xmit; + int count; + + if (port->x_char) { + pr_debug("wrote %2x", port->x_char); + port->icount.tx++; + port->x_char = 0; + return; + } + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + tiny_stop_tx(port, 0); + return; + } + + count = port->fifosize >> 1; + do { + pr_debug("wrote %2x", xmit->buf[xmit->tail]); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + if (uart_circ_empty(xmit)) + break; + } while (--count > 0); + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (uart_circ_empty(xmit)) + tiny_stop_tx(port, 0); +} + +static void tiny_start_tx(struct uart_port *port, unsigned int tty_start) +{ +} + +static void tiny_timer(unsigned long data) +{ + struct uart_port *port; + struct tty_struct *tty; + + + port = (struct uart_port *)data; + if (!port) + return; + if (!port->info) + return; + tty = port->info->tty; + if (!tty) + return; + + /* add one character to the tty port */ + /* this doesn't actually push the data through unless tty->low_latency is set */ + tty_insert_flip_char(tty, TINY_DATA_CHARACTER, 0); + + tty_flip_buffer_push(tty); + + /* resubmit the timer again */ + timer->expires = jiffies + DELAY_TIME; + add_timer(timer); + + /* see if we have any data to transmit */ + tiny_tx_chars(port); +} + +static unsigned int tiny_tx_empty(struct uart_port *port) +{ + return 0; +} + +static unsigned int tiny_get_mctrl(struct uart_port *port) +{ + return 0; +} + +static void tiny_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +} + +static void tiny_break_ctl(struct uart_port *port, int break_state) +{ +} + +static void tiny_set_termios(struct uart_port *port, + struct termios *new, struct termios *old) +{ + int baud, quot, cflag = new->c_cflag; + /* get the byte size */ + switch (cflag & CSIZE) { + case CS5: + printk(KERN_DEBUG " - data bits = 5\n"); + break; + case CS6: + printk(KERN_DEBUG " - data bits = 6\n"); + break; + case CS7: + printk(KERN_DEBUG " - data bits = 7\n"); + break; + default: // CS8 + printk(KERN_DEBUG " - data bits = 8\n"); + break; + } + + /* determine the parity */ + if (cflag & PARENB) + if (cflag & PARODD) + pr_debug(" - parity = odd\n"); + else + pr_debug(" - parity = even\n"); + else + pr_debug(" - parity = none\n"); + + /* figure out the stop bits requested */ + if (cflag & CSTOPB) + pr_debug(" - stop bits = 2\n"); + else + pr_debug(" - stop bits = 1\n"); + + /* figure out the flow control settings */ + if (cflag & CRTSCTS) + pr_debug(" - RTS/CTS is enabled\n"); + else + pr_debug(" - RTS/CTS is disabled\n"); + + /* Set baud rate */ + baud = uart_get_baud_rate(port, new, old, 0, port->uartclk/16); + quot = uart_get_divisor(port, baud); + + //UART_PUT_DIV_LO(port, (quot & 0xff)); + //UART_PUT_DIV_HI(port, ((quot & 0xf00) >> 8)); +} + +static int tiny_startup(struct uart_port *port) +{ + /* this is the first time this port is opened */ + /* do any hardware initialization needed here */ + + /* create our timer and submit it */ + if (!timer) { + timer = kmalloc(sizeof(*timer), GFP_KERNEL); + if (!timer) + return -ENOMEM; + } + timer->data = (unsigned long)port; + timer->expires = jiffies + DELAY_TIME; + timer->function = tiny_timer; + add_timer(timer); + return 0; +} + +static void tiny_shutdown(struct uart_port *port) +{ + /* The port is being closed by the last user. */ + /* Do any hardware specific stuff here */ + + /* shut down our timer */ + del_timer(timer); +} + +static const char *tiny_type(struct uart_port *port) +{ + return "tinytty"; +} + +static void tiny_release_port(struct uart_port *port) +{ + +} + +static int tiny_request_port(struct uart_port *port) +{ + return 0; +} + +static void tiny_config_port(struct uart_port *port, int flags) +{ +} + +static int tiny_verify_port(struct uart_port *port, struct serial_struct *ser) +{ + return 0; +} + +static struct uart_ops tiny_ops = { + .tx_empty = tiny_tx_empty, + .set_mctrl = tiny_set_mctrl, + .get_mctrl = tiny_get_mctrl, + .stop_tx = tiny_stop_tx, + .start_tx = tiny_start_tx, + .stop_rx = tiny_stop_rx, + .enable_ms = tiny_enable_ms, + .break_ctl = tiny_break_ctl, + .startup = tiny_startup, + .shutdown = tiny_shutdown, + .set_termios = tiny_set_termios, + .type = tiny_type, + .release_port = tiny_release_port, + .request_port = tiny_request_port, + .config_port = tiny_config_port, + .verify_port = tiny_verify_port, +}; + +static struct uart_port tiny_port = { + .ops = &tiny_ops, +}; + +static struct uart_driver tiny_reg = { + .owner = THIS_MODULE, + .driver_name = TINY_SERIAL_NAME, + .dev_name = TINY_SERIAL_NAME, + .major = TINY_SERIAL_MAJOR, + .minor = TINY_SERIAL_MINORS, + .nr = UART_NR, +}; + +static int __init tiny_init(void) +{ + int result; + + printk(KERN_INFO "Tiny serial driver loaded\n"); + + result = uart_register_driver(&tiny_reg); + if (result) + return result; + + result = uart_add_one_port(&tiny_reg, &tiny_port); + if (result) + uart_unregister_driver(&tiny_reg); + + return result; +} + +module_init(tiny_init); diff --git a/tty/tiny_tty.c b/tty/tiny_tty.c new file mode 100644 index 0000000..778b207 --- /dev/null +++ b/tty/tiny_tty.c @@ -0,0 +1,591 @@ +/* + * Tiny TTY driver + * + * Copyright (C) 2002-2004 Greg Kroah-Hartman (greg@kroah.com) + * + * 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, version 2 of the License. + * + * This driver shows how to create a minimal tty driver. It does not rely on + * any backing hardware, but creates a timer that emulates data being received + * from some kind of hardware. + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <asm/uaccess.h> + + +#define DRIVER_VERSION "v2.0" +#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>" +#define DRIVER_DESC "Tiny TTY driver" + +/* Module information */ +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); + +#define DELAY_TIME HZ * 2 /* 2 seconds per character */ +#define TINY_DATA_CHARACTER 't' + +#define TINY_TTY_MAJOR 240 /* experimental range */ +#define TINY_TTY_MINORS 4 /* only have 4 devices */ + +struct tiny_serial { + struct tty_struct *tty; /* pointer to the tty for this device */ + int open_count; /* number of times this port has been opened */ + struct semaphore sem; /* locks this structure */ + struct timer_list *timer; + + /* for tiocmget and tiocmset functions */ + int msr; /* MSR shadow */ + int mcr; /* MCR shadow */ + + /* for ioctl fun */ + struct serial_struct serial; + wait_queue_head_t wait; + struct async_icount icount; +}; + +static struct tiny_serial *tiny_table[TINY_TTY_MINORS]; /* initially all NULL */ + + +static void tiny_timer(unsigned long timer_data) +{ + struct tiny_serial *tiny = (struct tiny_serial *)timer_data; + struct tty_struct *tty; + int i; + char data[1] = {TINY_DATA_CHARACTER}; + int data_size = 1; + + if (!tiny) + return; + + tty = tiny->tty; + + /* send the data to the tty layer for users to read. This doesn't + * actually push the data through unless tty->low_latency is set */ + for (i = 0; i < data_size; ++i) { + if (tty->flip.count >= TTY_FLIPBUF_SIZE) + tty_flip_buffer_push(tty); + tty_insert_flip_char(tty, data[i], TTY_NORMAL); + } + tty_flip_buffer_push(tty); + + /* resubmit the timer again */ + tiny->timer->expires = jiffies + DELAY_TIME; + add_timer(tiny->timer); +} + +static int tiny_open(struct tty_struct *tty, struct file *file) +{ + struct tiny_serial *tiny; + struct timer_list *timer; + int index; + + /* initialize the pointer in case something fails */ + tty->driver_data = NULL; + + /* get the serial object associated with this tty pointer */ + index = tty->index; + tiny = tiny_table[index]; + if (tiny == NULL) { + /* first time accessing this device, let's create it */ + tiny = kmalloc(sizeof(*tiny), GFP_KERNEL); + if (!tiny) + return -ENOMEM; + + init_MUTEX(&tiny->sem); + tiny->open_count = 0; + tiny->timer = NULL; + + tiny_table[index] = tiny; + } + + down(&tiny->sem); + + /* save our structure within the tty structure */ + tty->driver_data = tiny; + tiny->tty = tty; + + ++tiny->open_count; + if (tiny->open_count == 1) { + /* this is the first time this port is opened */ + /* do any hardware initialization needed here */ + + /* create our timer and submit it */ + if (!tiny->timer) { + timer = kmalloc(sizeof(*timer), GFP_KERNEL); + if (!timer) { + up(&tiny->sem); + return -ENOMEM; + } + tiny->timer = timer; + } + tiny->timer->data = (unsigned long )tiny; + tiny->timer->expires = jiffies + DELAY_TIME; + tiny->timer->function = tiny_timer; + add_timer(tiny->timer); + } + + up(&tiny->sem); + return 0; +} + +static void do_close(struct tiny_serial *tiny) +{ + down(&tiny->sem); + + if (!tiny->open_count) { + /* port was never opened */ + goto exit; + } + + --tiny->open_count; + if (tiny->open_count <= 0) { + /* The port is being closed by the last user. */ + /* Do any hardware specific stuff here */ + + /* shut down our timer */ + del_timer(tiny->timer); + } +exit: + up(&tiny->sem); +} + +static void tiny_close(struct tty_struct *tty, struct file *file) +{ + struct tiny_serial *tiny = tty->driver_data; + + if (tiny) + do_close(tiny); +} + +static int tiny_write(struct tty_struct *tty, + const unsigned char *buffer, int count) +{ + struct tiny_serial *tiny = tty->driver_data; + int i; + int retval = -EINVAL; + + if (!tiny) + return -ENODEV; + + down(&tiny->sem); + + if (!tiny->open_count) + /* port was not opened */ + goto exit; + + /* fake sending the data out a hardware port by + * writing it to the kernel debug log. + */ + printk(KERN_DEBUG "%s - ", __FUNCTION__); + for (i = 0; i < count; ++i) + printk("%02x ", buffer[i]); + printk("\n"); + +exit: + up(&tiny->sem); + return retval; +} + +static int tiny_write_room(struct tty_struct *tty) +{ + struct tiny_serial *tiny = tty->driver_data; + int room = -EINVAL; + + if (!tiny) + return -ENODEV; + + down(&tiny->sem); + + if (!tiny->open_count) { + /* port was not opened */ + goto exit; + } + + /* calculate how much room is left in the device */ + room = 255; + +exit: + up(&tiny->sem); + return room; +} + +#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) + +static void tiny_set_termios(struct tty_struct *tty, struct termios *old_termios) +{ + unsigned int cflag; + + cflag = tty->termios->c_cflag; + + /* check that they really want us to change something */ + if (old_termios) { + if ((cflag == old_termios->c_cflag) && + (RELEVANT_IFLAG(tty->termios->c_iflag) == + RELEVANT_IFLAG(old_termios->c_iflag))) { + printk(KERN_DEBUG " - nothing to change...\n"); + return; + } + } + + /* get the byte size */ + switch (cflag & CSIZE) { + case CS5: + printk(KERN_DEBUG " - data bits = 5\n"); + break; + case CS6: + printk(KERN_DEBUG " - data bits = 6\n"); + break; + case CS7: + printk(KERN_DEBUG " - data bits = 7\n"); + break; + default: + case CS8: + printk(KERN_DEBUG " - data bits = 8\n"); + break; + } + + /* determine the parity */ + if (cflag & PARENB) + if (cflag & PARODD) + printk(KERN_DEBUG " - parity = odd\n"); + else + printk(KERN_DEBUG " - parity = even\n"); + else + printk(KERN_DEBUG " - parity = none\n"); + + /* figure out the stop bits requested */ + if (cflag & CSTOPB) + printk(KERN_DEBUG " - stop bits = 2\n"); + else + printk(KERN_DEBUG " - stop bits = 1\n"); + + /* figure out the hardware flow control settings */ + if (cflag & CRTSCTS) + printk(KERN_DEBUG " - RTS/CTS is enabled\n"); + else + printk(KERN_DEBUG " - RTS/CTS is disabled\n"); + + /* determine software flow control */ + /* if we are implementing XON/XOFF, set the start and + * stop character in the device */ + if (I_IXOFF(tty) || I_IXON(tty)) { + unsigned char stop_char = STOP_CHAR(tty); + unsigned char start_char = START_CHAR(tty); + + /* if we are implementing INBOUND XON/XOFF */ + if (I_IXOFF(tty)) + printk(KERN_DEBUG " - INBOUND XON/XOFF is enabled, " + "XON = %2x, XOFF = %2x", start_char, stop_char); + else + printk(KERN_DEBUG" - INBOUND XON/XOFF is disabled"); + + /* if we are implementing OUTBOUND XON/XOFF */ + if (I_IXON(tty)) + printk(KERN_DEBUG" - OUTBOUND XON/XOFF is enabled, " + "XON = %2x, XOFF = %2x", start_char, stop_char); + else + printk(KERN_DEBUG" - OUTBOUND XON/XOFF is disabled"); + } + + /* get the baud rate wanted */ + printk(KERN_DEBUG " - baud rate = %d", tty_get_baud_rate(tty)); +} + +/* Our fake UART values */ +#define MCR_DTR 0x01 +#define MCR_RTS 0x02 +#define MCR_LOOP 0x04 +#define MSR_CTS 0x08 +#define MSR_CD 0x10 +#define MSR_RI 0x20 +#define MSR_DSR 0x40 + +static int tiny_tiocmget(struct tty_struct *tty, struct file *file) +{ + struct tiny_serial *tiny = tty->driver_data; + + unsigned int result = 0; + unsigned int msr = tiny->msr; + unsigned int mcr = tiny->mcr; + + result = ((mcr & MCR_DTR) ? TIOCM_DTR : 0) | /* DTR is set */ + ((mcr & MCR_RTS) ? TIOCM_RTS : 0) | /* RTS is set */ + ((mcr & MCR_LOOP) ? TIOCM_LOOP : 0) | /* LOOP is set */ + ((msr & MSR_CTS) ? TIOCM_CTS : 0) | /* CTS is set */ + ((msr & MSR_CD) ? TIOCM_CAR : 0) | /* Carrier detect is set*/ + ((msr & MSR_RI) ? TIOCM_RI : 0) | /* Ring Indicator is set */ + ((msr & MSR_DSR) ? TIOCM_DSR : 0); /* DSR is set */ + + return result; +} + +static int tiny_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + struct tiny_serial *tiny = tty->driver_data; + unsigned int mcr = tiny->mcr; + + if (set & TIOCM_RTS) + mcr |= MCR_RTS; + if (set & TIOCM_DTR) + mcr |= MCR_RTS; + + if (clear & TIOCM_RTS) + mcr &= ~MCR_RTS; + if (clear & TIOCM_DTR) + mcr &= ~MCR_RTS; + + /* set the new MCR value in the device */ + tiny->mcr = mcr; + return 0; +} + +static int tiny_read_proc(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + struct tiny_serial *tiny; + off_t begin = 0; + int length = 0; + int i; + + length += sprintf(page, "tinyserinfo:1.0 driver:%s\n", DRIVER_VERSION); + for (i = 0; i < TINY_TTY_MINORS && length < PAGE_SIZE; ++i) { + tiny = tiny_table[i]; + if (tiny == NULL) + continue; + + length += sprintf(page+length, "%d\n", i); + if ((length + begin) > (off + count)) + goto done; + if ((length + begin) < off) { + begin += length; + length = 0; + } + } + *eof = 1; +done: + if (off >= (length + begin)) + return 0; + *start = page + (off-begin); + return (count < begin+length-off) ? count : begin + length-off; +} + +#define tiny_ioctl tiny_ioctl_tiocgserial +static int tiny_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct tiny_serial *tiny = tty->driver_data; + + if (cmd == TIOCGSERIAL) { + struct serial_struct tmp; + + if (!arg) + return -EFAULT; + + memset(&tmp, 0, sizeof(tmp)); + + tmp.type = tiny->serial.type; + tmp.line = tiny->serial.line; + tmp.port = tiny->serial.port; + tmp.irq = tiny->serial.irq; + tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; + tmp.xmit_fifo_size = tiny->serial.xmit_fifo_size; + tmp.baud_base = tiny->serial.baud_base; + tmp.close_delay = 5*HZ; + tmp.closing_wait = 30*HZ; + tmp.custom_divisor = tiny->serial.custom_divisor; + tmp.hub6 = tiny->serial.hub6; + tmp.io_type = tiny->serial.io_type; + + if (copy_to_user((void __user *)arg, &tmp, sizeof(struct serial_struct))) + return -EFAULT; + return 0; + } + return -ENOIOCTLCMD; +} +#undef tiny_ioctl + +#define tiny_ioctl tiny_ioctl_tiocmiwait +static int tiny_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct tiny_serial *tiny = tty->driver_data; + + if (cmd == TIOCMIWAIT) { + DECLARE_WAITQUEUE(wait, current); + struct async_icount cnow; + struct async_icount cprev; + + cprev = tiny->icount; + while (1) { + add_wait_queue(&tiny->wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + remove_wait_queue(&tiny->wait, &wait); + + /* see if a signal woke us up */ + if (signal_pending(current)) + return -ERESTARTSYS; + + cnow = tiny->icount; + if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr && + cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) + return -EIO; /* no change => error */ + if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || + ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || + ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || + ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) { + return 0; + } + cprev = cnow; + } + + } + return -ENOIOCTLCMD; +} +#undef tiny_ioctl + +#define tiny_ioctl tiny_ioctl_tiocgicount +static int tiny_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct tiny_serial *tiny = tty->driver_data; + + if (cmd == TIOCGICOUNT) { + struct async_icount cnow = tiny->icount; + struct serial_icounter_struct icount; + + icount.cts = cnow.cts; + icount.dsr = cnow.dsr; + icount.rng = cnow.rng; + icount.dcd = cnow.dcd; + icount.rx = cnow.rx; + icount.tx = cnow.tx; + icount.frame = cnow.frame; + icount.overrun = cnow.overrun; + icount.parity = cnow.parity; + icount.brk = cnow.brk; + icount.buf_overrun = cnow.buf_overrun; + + if (copy_to_user((void __user *)arg, &icount, sizeof(icount))) + return -EFAULT; + return 0; + } + return -ENOIOCTLCMD; +} +#undef tiny_ioctl + +/* the real tiny_ioctl function. The above is done to get the small functions in the book */ +static int tiny_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case TIOCGSERIAL: + return tiny_ioctl_tiocgserial(tty, file, cmd, arg); + case TIOCMIWAIT: + return tiny_ioctl_tiocmiwait(tty, file, cmd, arg); + case TIOCGICOUNT: + return tiny_ioctl_tiocgicount(tty, file, cmd, arg); + } + + return -ENOIOCTLCMD; +} + +static struct tty_operations serial_ops = { + .open = tiny_open, + .close = tiny_close, + .write = tiny_write, + .write_room = tiny_write_room, + .set_termios = tiny_set_termios, +}; + +static struct tty_driver *tiny_tty_driver; + +static int __init tiny_init(void) +{ + int retval; + int i; + + /* allocate the tty driver */ + tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS); + if (!tiny_tty_driver) + return -ENOMEM; + + /* initialize the tty driver */ + tiny_tty_driver->owner = THIS_MODULE; + tiny_tty_driver->driver_name = "tiny_tty"; + tiny_tty_driver->name = "ttty"; + tiny_tty_driver->devfs_name = "tts/ttty%d"; + tiny_tty_driver->major = TINY_TTY_MAJOR, + tiny_tty_driver->type = TTY_DRIVER_TYPE_SERIAL, + tiny_tty_driver->subtype = SERIAL_TYPE_NORMAL, + tiny_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS, + tiny_tty_driver->init_termios = tty_std_termios; + tiny_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + tty_set_operations(tiny_tty_driver, &serial_ops); + + /* hack to make the book purty, yet still use these functions in the + * real driver. They really should be set up in the serial_ops + * structure above... */ + tiny_tty_driver->read_proc = tiny_read_proc; + tiny_tty_driver->tiocmget = tiny_tiocmget; + tiny_tty_driver->tiocmset = tiny_tiocmset; + tiny_tty_driver->ioctl = tiny_ioctl; + + /* register the tty driver */ + retval = tty_register_driver(tiny_tty_driver); + if (retval) { + printk(KERN_ERR "failed to register tiny tty driver"); + put_tty_driver(tiny_tty_driver); + return retval; + } + + for (i = 0; i < TINY_TTY_MINORS; ++i) + tty_register_device(tiny_tty_driver, i, NULL); + + printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION); + return retval; +} + +static void __exit tiny_exit(void) +{ + struct tiny_serial *tiny; + int i; + + for (i = 0; i < TINY_TTY_MINORS; ++i) + tty_unregister_device(tiny_tty_driver, i); + tty_unregister_driver(tiny_tty_driver); + + /* shut down all of the timers and free the memory */ + for (i = 0; i < TINY_TTY_MINORS; ++i) { + tiny = tiny_table[i]; + if (tiny) { + /* close the port */ + while (tiny->open_count) + do_close(tiny); + + /* shut down our timer and free the memory */ + del_timer(tiny->timer); + kfree(tiny->timer); + kfree(tiny); + tiny_table[i] = NULL; + } + } +} + +module_init(tiny_init); +module_exit(tiny_exit); diff --git a/usb/Makefile b/usb/Makefile new file mode 100644 index 0000000..028111f --- /dev/null +++ b/usb/Makefile @@ -0,0 +1,11 @@ +obj-m := usb-skeleton.o + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +all: + $(MAKE) -C $(KERNELDIR) M=$(PWD) + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + diff --git a/usb/usb-skeleton.c b/usb/usb-skeleton.c new file mode 100644 index 0000000..625bc0b --- /dev/null +++ b/usb/usb-skeleton.c @@ -0,0 +1,356 @@ +/* + * USB Skeleton driver - 2.0 + * + * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) + * + * 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, version 2. + * + * This driver is based on the 2.6.3 version of drivers/usb/usb-skeleton.c + * but has been rewritten to be easy to read and use, as no locks are now + * needed anymore. + * + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kref.h> +#include <linux/smp_lock.h> +#include <linux/usb.h> +#include <asm/uaccess.h> + + +/* Define these values to match your devices */ +#define USB_SKEL_VENDOR_ID 0xfff0 +#define USB_SKEL_PRODUCT_ID 0xfff0 + +/* table of devices that work with this driver */ +static struct usb_device_id skel_table [] = { + { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE (usb, skel_table); + + +/* Get a minor range for your devices from the usb maintainer */ +#define USB_SKEL_MINOR_BASE 192 + +/* Structure to hold all of our device specific stuff */ +struct usb_skel { + struct usb_device * udev; /* the usb device for this device */ + struct usb_interface * interface; /* the interface for this device */ + unsigned char * bulk_in_buffer; /* the buffer to receive data */ + size_t bulk_in_size; /* the size of the receive buffer */ + __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */ + __u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */ + struct kref kref; +}; +#define to_skel_dev(d) container_of(d, struct usb_skel, kref) + +static struct usb_driver skel_driver; + +static void skel_delete(struct kref *kref) +{ + struct usb_skel *dev = to_skel_dev(kref); + + usb_put_dev(dev->udev); + kfree (dev->bulk_in_buffer); + kfree (dev); +} + +static int skel_open(struct inode *inode, struct file *file) +{ + struct usb_skel *dev; + struct usb_interface *interface; + int subminor; + int retval = 0; + + subminor = iminor(inode); + + interface = usb_find_interface(&skel_driver, subminor); + if (!interface) { + err ("%s - error, can't find device for minor %d", + __FUNCTION__, subminor); + retval = -ENODEV; + goto exit; + } + + dev = usb_get_intfdata(interface); + if (!dev) { + retval = -ENODEV; + goto exit; + } + + /* increment our usage count for the device */ + kref_get(&dev->kref); + + /* save our object in the file's private structure */ + file->private_data = dev; + +exit: + return retval; +} + +static int skel_release(struct inode *inode, struct file *file) +{ + struct usb_skel *dev; + + dev = (struct usb_skel *)file->private_data; + if (dev == NULL) + return -ENODEV; + + /* decrement the count on our device */ + kref_put(&dev->kref, skel_delete); + return 0; +} + +static ssize_t skel_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct usb_skel *dev; + int retval = 0; + + dev = (struct usb_skel *)file->private_data; + + /* do a blocking bulk read to get data from the device */ + retval = usb_bulk_msg(dev->udev, + usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr), + dev->bulk_in_buffer, + min(dev->bulk_in_size, count), + &count, HZ*10); + + /* if the read was successful, copy the data to userspace */ + if (!retval) { + if (copy_to_user(buffer, dev->bulk_in_buffer, count)) + retval = -EFAULT; + else + retval = count; + } + + return retval; +} + +static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs) +{ + /* sync/async unlink faults aren't errors */ + if (urb->status && + !(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) { + dbg("%s - nonzero write bulk status received: %d", + __FUNCTION__, urb->status); + } + + /* free up our allocated buffer */ + usb_buffer_free(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); +} + +static ssize_t skel_write(struct file *file, const char __user *user_buffer, size_t count, loff_t *ppos) +{ + struct usb_skel *dev; + int retval = 0; + struct urb *urb = NULL; + char *buf = NULL; + + dev = (struct usb_skel *)file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto exit; + + /* create a urb, and a buffer for it, and copy the data to the urb */ + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + retval = -ENOMEM; + goto error; + } + + buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma); + if (!buf) { + retval = -ENOMEM; + goto error; + } + if (copy_from_user(buf, user_buffer, count)) { + retval = -EFAULT; + goto error; + } + + /* initialize the urb properly */ + usb_fill_bulk_urb(urb, dev->udev, + usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr), + buf, count, skel_write_bulk_callback, dev); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* send the data out the bulk port */ + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval) { + err("%s - failed submitting write urb, error %d", __FUNCTION__, retval); + goto error; + } + + /* release our reference to this urb, the USB core will eventually free it entirely */ + usb_free_urb(urb); + +exit: + return count; + +error: + usb_buffer_free(dev->udev, count, buf, urb->transfer_dma); + usb_free_urb(urb); + kfree(buf); + return retval; +} + +static struct file_operations skel_fops = { + .owner = THIS_MODULE, + .read = skel_read, + .write = skel_write, + .open = skel_open, + .release = skel_release, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with devfs and the driver core + */ +static struct usb_class_driver skel_class = { + .name = "usb/skel%d", + .fops = &skel_fops, + .mode = S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, + .minor_base = USB_SKEL_MINOR_BASE, +}; + +static int skel_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + struct usb_skel *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + size_t buffer_size; + int i; + int retval = -ENOMEM; + + /* allocate memory for our device state and initialize it */ + dev = kmalloc(sizeof(struct usb_skel), GFP_KERNEL); + if (dev == NULL) { + err("Out of memory"); + goto error; + } + memset(dev, 0x00, sizeof (*dev)); + kref_init(&dev->kref); + + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + dev->interface = interface; + + /* set up the endpoint information */ + /* use only the first bulk-in and bulk-out endpoints */ + iface_desc = interface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (!dev->bulk_in_endpointAddr && + (endpoint->bEndpointAddress & USB_DIR_IN) && + ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + == USB_ENDPOINT_XFER_BULK)) { + /* we found a bulk in endpoint */ + buffer_size = endpoint->wMaxPacketSize; + dev->bulk_in_size = buffer_size; + dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; + dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!dev->bulk_in_buffer) { + err("Could not allocate bulk_in_buffer"); + goto error; + } + } + + if (!dev->bulk_out_endpointAddr && + !(endpoint->bEndpointAddress & USB_DIR_IN) && + ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + == USB_ENDPOINT_XFER_BULK)) { + /* we found a bulk out endpoint */ + dev->bulk_out_endpointAddr = endpoint->bEndpointAddress; + } + } + if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) { + err("Could not find both bulk-in and bulk-out endpoints"); + goto error; + } + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* we can register the device now, as it is ready */ + retval = usb_register_dev(interface, &skel_class); + if (retval) { + /* something prevented us from registering this driver */ + err("Not able to get a minor for this device."); + usb_set_intfdata(interface, NULL); + goto error; + } + + /* let the user know what node this device is now attached to */ + info("USB Skeleton device now attached to USBSkel-%d", interface->minor); + return 0; + +error: + if (dev) + kref_put(&dev->kref, skel_delete); + return retval; +} + +static void skel_disconnect(struct usb_interface *interface) +{ + struct usb_skel *dev; + int minor = interface->minor; + + /* prevent skel_open() from racing skel_disconnect() */ + lock_kernel(); + + dev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + /* give back our minor */ + usb_deregister_dev(interface, &skel_class); + + unlock_kernel(); + + /* decrement our usage count */ + kref_put(&dev->kref, skel_delete); + + info("USB Skeleton #%d now disconnected", minor); +} + +static struct usb_driver skel_driver = { + .owner = THIS_MODULE, + .name = "skeleton", + .id_table = skel_table, + .probe = skel_probe, + .disconnect = skel_disconnect, +}; + +static int __init usb_skel_init(void) +{ + int result; + + /* register this driver with the USB subsystem */ + result = usb_register(&skel_driver); + if (result) + err("usb_register failed. Error number %d", result); + + return result; +} + +static void __exit usb_skel_exit(void) +{ + /* deregister this driver with the USB subsystem */ + usb_deregister(&skel_driver); +} + +module_init (usb_skel_init); +module_exit (usb_skel_exit); + +MODULE_LICENSE("GPL"); |