summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJavier Martinez Canillas <martinez.javier@gmail.com>2010-11-27 07:49:17 +0100
committerJavier Martinez Canillas <martinez.javier@gmail.com>2010-11-27 07:49:17 +0100
commitab121f379a3cff458c90e6f480ba4bb68c8733dd (patch)
treea9851af109ee83646d108bc247d03b131461b764
downloadldd3-ab121f379a3cff458c90e6f480ba4bb68c8733dd.tar.gz
Linux Device Drivers 3 examples
-rw-r--r--LICENSE27
-rw-r--r--Makefile12
-rw-r--r--include/lddbus.h39
-rw-r--r--lddbus/Makefile39
-rw-r--r--lddbus/lddbus.c177
-rw-r--r--misc-modules/Makefile32
-rw-r--r--misc-modules/complete.c81
-rw-r--r--misc-modules/faulty.c89
-rw-r--r--misc-modules/hello.c20
-rw-r--r--misc-modules/hellop.c40
-rw-r--r--misc-modules/jiq.c264
-rw-r--r--misc-modules/jit.c292
-rw-r--r--misc-modules/kdataalign.c69
-rw-r--r--misc-modules/kdatasize.c48
-rw-r--r--misc-modules/seq.c109
-rw-r--r--misc-modules/silly.c294
-rw-r--r--misc-modules/sleepy.c84
-rw-r--r--misc-progs/Makefile13
-rw-r--r--misc-progs/asynctest.c57
-rw-r--r--misc-progs/dataalign.c58
-rw-r--r--misc-progs/datasize.c35
-rw-r--r--misc-progs/gdbline19
-rw-r--r--misc-progs/inp.c129
-rw-r--r--misc-progs/load50.c38
-rw-r--r--misc-progs/mapcmp.c87
-rw-r--r--misc-progs/mapper.c71
-rw-r--r--misc-progs/nbtest.c44
-rw-r--r--misc-progs/netifdebug.c84
-rw-r--r--misc-progs/outp.c136
-rw-r--r--misc-progs/polltest.c47
-rw-r--r--misc-progs/setconsole.c42
-rw-r--r--misc-progs/setlevel.c47
-rw-r--r--pci/Makefile11
-rw-r--r--pci/pci_skel.c63
-rw-r--r--sbull/Makefile41
-rw-r--r--sbull/sbull.c456
-rw-r--r--sbull/sbull.h71
-rw-r--r--sbull/sbull_load47
-rw-r--r--sbull/sbull_unload14
-rw-r--r--scull/Makefile43
-rwxr-xr-xscull/SCULL~1.INI142
-rw-r--r--scull/access.c410
-rw-r--r--scull/main.c673
-rw-r--r--scull/pipe.c396
-rw-r--r--scull/scull.h177
-rw-r--r--scull/scull.init142
-rw-r--r--scull/scull_load66
-rw-r--r--scull/scull_unload20
-rw-r--r--scullc/Makefile46
-rw-r--r--scullc/main.c600
-rw-r--r--scullc/mmap.c118
-rw-r--r--scullc/scullc.h122
-rw-r--r--scullc/scullc_load30
-rw-r--r--scullc/scullc_unload11
-rw-r--r--sculld/Makefile46
-rw-r--r--sculld/main.c632
-rw-r--r--sculld/mmap.c118
-rw-r--r--sculld/sculld.h126
-rw-r--r--sculld/sculld_load30
-rw-r--r--sculld/sculld_unload11
-rw-r--r--scullp/Makefile46
-rw-r--r--scullp/main.c598
-rw-r--r--scullp/mmap.c119
-rw-r--r--scullp/scullp.h122
-rw-r--r--scullp/scullp_load30
-rw-r--r--scullp/scullp_unload11
-rw-r--r--scullv/Makefile46
-rw-r--r--scullv/main.c597
-rw-r--r--scullv/mmap.c120
-rw-r--r--scullv/scullv.h122
-rw-r--r--scullv/scullv_load30
-rw-r--r--scullv/scullv_unload11
-rw-r--r--short/Makefile40
-rw-r--r--short/short.c692
-rw-r--r--short/short_load61
-rw-r--r--short/short_unload16
-rw-r--r--shortprint/Makefile31
-rw-r--r--shortprint/shortprint.c521
-rw-r--r--shortprint/shortprint.h46
-rw-r--r--shortprint/shortprint_load31
-rw-r--r--shortprint/shortprint_unload15
-rw-r--r--simple/Makefile39
-rw-r--r--simple/simple.c235
-rw-r--r--simple/simple_load26
-rw-r--r--simple/simple_unload14
-rw-r--r--skull/Makefile1
-rw-r--r--skull/skull_clean.c22
-rw-r--r--skull/skull_init.c200
-rw-r--r--snull/Makefile41
-rwxr-xr-xsnull/SNULLO~1.CMD512
-rw-r--r--snull/snull.c736
-rw-r--r--snull/snull.h49
-rw-r--r--snull/snull_load8
-rw-r--r--snull/snull_unload5
-rw-r--r--tty/Makefile41
-rw-r--r--tty/tiny_serial.c291
-rw-r--r--tty/tiny_tty.c591
-rw-r--r--usb/Makefile11
-rw-r--r--usb/usb-skeleton.c356
99 files changed, 13768 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c1ff8b6
--- /dev/null
+++ b/LICENSE
@@ -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");