first commit

This commit is contained in:
LIU CHAO 2023-07-22 13:42:32 +08:00
commit d98782fd8b
155 changed files with 18098 additions and 0 deletions

30
device_model/Makefile Normal file
View File

@ -0,0 +1,30 @@
ifeq ($(DEBUG),y)
DEBFLAGS = -O -g -DDEBUG
endif
EXTRA_CFLAGS += $(DEBFLAGS)
KERNEL_DIR=../../ebf_6ull_linux
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
#xxx-objs := main.o pipe.o access.o
obj-m := my_bus.o my_device.o my_driver.o
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
.PHONE:clean
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
#depend .depend dep:
# $(CC) $(EXTRA_CFLAGS) -M *.c > .depend
#ifeq (.depend,$(wildcard .depend))
#include .depend
#endif

65
device_model/my_bus.c Normal file
View File

@ -0,0 +1,65 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include "pub.h"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liuchao");
static int my_bus_match(struct device *dev, struct device_driver *drv){
PDEBUG("%s-%s\n",__FILE__, __func__);
if(!strncmp(dev_name(dev), drv->name, strlen(drv->name)))
{
PDEBUG("dev & drv match\n");
}
}
static struct bus_type my_bus = {
.name = "my_bus",
.match = my_bus_match,
};
EXPORT_SYMBOL(my_bus);
static char *version = "$Revision: 1.9 $";
static ssize_t my_bus_test_show(struct bus_type *bus, char *buf)
{
return sprintf(buf, "%s\n", version);
}
static BUS_ATTR(Version, S_IRUSR, my_bus_test_show, NULL);
static __init int my_bus_init(void)
{
printk("my_bus init\n");
bus_register(&my_bus);
bus_create_file(&my_bus, &bus_attr_Version);
return 0;
}
module_init(my_bus_init);
static __exit void my_bus_exit(void)
{
printk("my_bus exit\n");
bus_remove_file(&my_bus, &bus_attr_Version);
bus_unregister(&my_bus);
}
module_exit(my_bus_exit);

70
device_model/my_device.c Normal file
View File

@ -0,0 +1,70 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include "pub.h"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liuchao");
extern struct bus_type my_bus;
void my_dev_release(struct device *dev)
{
printk("%s-%s\n", __FILE__, __func__);
}
static struct device my_dev = {
.init_name = "my_dev",
.bus = &my_bus,
.release = my_dev_release,
};
unsigned long id = 0;
ssize_t my_dev_id_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d\n", id);
}
ssize_t my_dev_id_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
kstrtoul(buf, 10, &id);
return count;
}
DEVICE_ATTR(my_dev_id, S_IRUSR|S_IWUSR, my_dev_id_show, my_dev_id_store);
static __init int my_dev_init(void)
{
printk("my_dev init\n");
device_register(&my_dev);
device_create_file(&my_dev, &dev_attr_my_dev_id);
return 0;
}
module_init(my_dev_init);
static __exit void my_dev_exit(void)
{
printk("xdev exit\n");
device_remove_file(&my_dev, &dev_attr_my_dev_id);
device_unregister(&my_dev);
}
module_exit(my_dev_exit);

69
device_model/my_driver.c Normal file
View File

@ -0,0 +1,69 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include "pub.h"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liuchao");
extern struct bus_type my_bus;
int my_drv_probe(struct device *dev)
{
printk("%s\n", dev_name(dev));
printk("%s-%s\n", __FILE__, __func__);
return 0;
}
int my_drv_remove(struct device *dev)
{
printk("%s\n", dev->init_name);
printk("%s-%s\n", __FILE__, __func__);
return 0;
}
static struct device_driver my_drv = {
.name = "my_dev",
.bus = &my_bus,
.probe = my_drv_probe,
.remove = my_drv_remove,
};
char *name = "my_drv";
ssize_t drvname_show(struct device_driver *drv, char *buf)
{
return sprintf(buf, "%s\n", name);
}
DRIVER_ATTR_RO(drvname);
static __init int my_drv_init(void)
{
printk("my_drv init\n");
driver_register(&my_drv);
driver_create_file(&my_drv, &driver_attr_drvname);
return 0;
}
module_init(my_drv_init);
static __exit void my_drv_exit(void)
{
printk("my_drv exit\n");
driver_remove_file(&my_drv, &driver_attr_drvname);
driver_unregister(&my_drv);
}
module_exit(my_drv_exit);

11
device_model/pub.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef _PUB_H
#define _PUB_H
#ifdef DEBUG
#define PDEBUG(fmt, args...) printk(KERN_INFO "device model:: " fmt, ##args)
#else
#define PDEBUG(fmt, args...)
#endif
#endif

30
device_tree/Makefile Normal file
View File

@ -0,0 +1,30 @@
ifeq ($(DEBUG),y)
DEBFLAGS = -O -g -DDEBUG
endif
EXTRA_CFLAGS += $(DEBFLAGS)
KERNEL_DIR=../../ebf_6ull_linux
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
#xxx-objs := main.o pipe.o access.o
obj-m := dt_plat_drv.o
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
.PHONE:clean
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
#depend .depend dep:
# $(CC) $(EXTRA_CFLAGS) -M *.c > .depend
#ifeq (.depend,$(wildcard .depend))
#include .depend
#endif

317
device_tree/dt_plat_drv.c Normal file
View File

@ -0,0 +1,317 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
struct led_chrdev {
unsigned int __iomem *va_dr;
unsigned int __iomem *va_gdir;
unsigned int __iomem *va_iomuxc_mux;
unsigned int __iomem *va_ccm_ccgrx;
unsigned int __iomem *va_iomux_pad;
unsigned int led_pin;
unsigned int clock_offset;
struct cdev dev;
dev_t my_devno;
};
static struct class *led_class = NULL;
static int led_open(struct inode *inode, struct file *filp)
{
uint32_t val = 0;
struct led_chrdev *my_led_dev = NULL;
printk("led open %d:%d...\n",MAJOR(inode->i_rdev),MINOR(inode->i_rdev));
my_led_dev = container_of(inode->i_cdev, struct led_chrdev, dev);
filp->private_data = my_led_dev;
val = ioread32(my_led_dev->va_ccm_ccgrx);
val &= ~(3 << my_led_dev->clock_offset);
val |= 3 << my_led_dev->clock_offset;
iowrite32(val, my_led_dev->va_ccm_ccgrx);
iowrite32(5, my_led_dev->va_iomuxc_mux);
iowrite32(0x1F838, my_led_dev->va_iomux_pad);
val = ioread32(my_led_dev->va_gdir);
val &= ~(1 << my_led_dev->led_pin);
val |= (1 << my_led_dev->led_pin);
iowrite32(val, my_led_dev->va_gdir);
val = ioread32(my_led_dev->va_dr);
val |= (0x01 << my_led_dev->led_pin);
iowrite32(val, my_led_dev->va_dr);
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return cnt;
}
static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt)
{
uint32_t val = 0;
uint32_t ret = 0;
struct led_chrdev *my_led_dev = NULL;
printk("led write...\n");
my_led_dev = filp->private_data;
kstrtoul_from_user(buf,cnt,10,&ret);
//printk("ret = %u\n",ret);
val = ioread32(my_led_dev->va_dr);
if (ret)
val &= ~(0x01 << my_led_dev->led_pin);
else
val |= (0x01 << my_led_dev->led_pin);
iowrite32(val, my_led_dev->va_dr);
*offt += cnt;
return cnt;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
static struct platform_device_id led_pdev_ids[] = {
{.name = "led_pdev"},
{}
};
MODULE_DEVICE_TABLE(platform, led_pdev_ids);
int led_plat_probe(struct platform_device *pdev)
{
uint32_t val = 0;
int ret = 0;
dev_t devno = 0;
struct led_chrdev *my_led_dev = NULL;
struct device_node *dev_node = NULL;
char *compatible_str = NULL;
unsigned int *led_hwinfo;
dev_node = dev_of_node(&(pdev->dev));
struct resource *mem_dr;
struct resource *mem_gdir;
struct resource *mem_iomuxc_mux;
struct resource *mem_ccm_ccgrx;
struct resource *mem_iomux_pad;
struct resource t_mem_dr;
struct resource t_mem_gdir;
struct resource t_mem_iomuxc_mux;
struct resource t_mem_ccm_ccgrx;
struct resource t_mem_iomux_pad;
if(NULL == dev_node){
printk(KERN_INFO"probe frome normal platform device\n");
led_hwinfo = dev_get_platdata(&(pdev->dev));
mem_dr = platform_get_resource(pdev,IORESOURCE_MEM,0);
mem_gdir = platform_get_resource(pdev,IORESOURCE_MEM,1);
mem_iomuxc_mux = platform_get_resource(pdev,IORESOURCE_MEM,2);
mem_ccm_ccgrx = platform_get_resource(pdev,IORESOURCE_MEM,3);
mem_iomux_pad = platform_get_resource(pdev,IORESOURCE_MEM,4);
}
else{
printk(KERN_INFO"probe frome device tree\n");
led_hwinfo = devm_kzalloc(&(pdev->dev),2*sizeof(unsigned int), GFP_KERNEL);
if(!led_hwinfo){
printk(KERN_ERR"devm_kzalloc failed\n");
return -ENOMEM;
}
if (0 != of_property_read_u32_array(dev_node, "hw_info", led_hwinfo, 2)){
printk(KERN_ERR"read hw_info err\n");
return -ENODATA;
}
of_address_to_resource(dev_node,3,&t_mem_dr);
of_address_to_resource(dev_node,4,&t_mem_gdir);
of_address_to_resource(dev_node,1,&t_mem_iomuxc_mux);
of_address_to_resource(dev_node,0,&t_mem_ccm_ccgrx);
of_address_to_resource(dev_node,2,&t_mem_iomux_pad);
mem_dr = &t_mem_dr;
mem_gdir = &t_mem_gdir;
mem_iomuxc_mux = &t_mem_iomuxc_mux;
mem_ccm_ccgrx = &t_mem_ccm_ccgrx;
mem_iomux_pad = &t_mem_iomux_pad;
if(0 != of_property_read_string(dev_node,"compatible",&compatible_str)){
printk(KERN_ERR"read compatible err\n");
return -ENODATA;
}
printk(KERN_INFO"compatible = %s\n",compatible_str);
if(0 == strcmp(compatible_str, "fire,rgb_led_red")){
printk(KERN_INFO"red led\n");
pdev->id = 0;
}
else if(0 == strcmp(compatible_str, "fire,rgb_led_green")){
printk(KERN_INFO"green led\n");
pdev->id = 1;
}
else{
printk(KERN_INFO"blue led\n");
pdev->id = 2;
}
}
my_led_dev = devm_kzalloc(&(pdev->dev),sizeof(struct led_chrdev), GFP_KERNEL);
if(!my_led_dev)
return -ENOMEM;
my_led_dev->led_pin = led_hwinfo[0];
my_led_dev->clock_offset = led_hwinfo[1];
my_led_dev->va_ccm_ccgrx = ioremap(mem_ccm_ccgrx->start,4);
my_led_dev->va_dr = ioremap(mem_dr->start,4);
my_led_dev->va_gdir = ioremap(mem_gdir->start,4);
my_led_dev->va_iomuxc_mux = ioremap(mem_iomuxc_mux->start,4);
my_led_dev->va_iomux_pad = ioremap(mem_iomux_pad->start,4);
printk(KERN_INFO"hw_info:%u,%d\n",my_led_dev->led_pin,my_led_dev->clock_offset);
printk(KERN_INFO"reg:0x%x,0x%x,0x%x,0x%x,0x%x,\n",mem_ccm_ccgrx->start,\
mem_dr->start,\
mem_gdir->start,\
mem_iomuxc_mux->start,\
mem_iomux_pad->start);
ret = alloc_chrdev_region(&devno, pdev->id, 1, "led");
if(ret < 0){
printk(KERN_ERR "chrdev alloc region failed\n");
return ret;
}
printk(KERN_INFO "devno is %u(major:%u,minor:%u)\n",devno,MAJOR(devno), MINOR(devno));
my_led_dev->my_devno = devno;
cdev_init(&my_led_dev->dev, &led_fops);
my_led_dev->dev.owner = THIS_MODULE;
ret = cdev_add(&my_led_dev->dev, devno, 1);
if(ret < 0){
printk(KERN_ERR "chrdev add failed\n");
goto add_err;
}
device_create(led_class,NULL,devno,NULL,"led_%s",(pdev->id == 0)?"red":((pdev->id == 1)? "green":"blue"));
platform_set_drvdata(pdev, my_led_dev);
return 0;
add_err:
unregister_chrdev_region(my_led_dev, 1);
return ret;
}
int led_plat_remove(struct platform_device *pdev)
{
dev_t cur_dev;
struct led_chrdev *my_led_dev = platform_get_drvdata(pdev);
printk("led platform driver remove\n");
iounmap(my_led_dev->va_ccm_ccgrx);
iounmap(my_led_dev->va_dr);
iounmap(my_led_dev->va_gdir);
iounmap(my_led_dev->va_iomuxc_mux);
iounmap(my_led_dev->va_iomux_pad);
cur_dev = my_led_dev->my_devno;
cdev_del(&my_led_dev->dev);
device_destroy(led_class, cur_dev);
unregister_chrdev_region(cur_dev, 1);
return 0;
}
static const struct of_device_id rgb_led[] = {
{.compatible = "fire,rgb_led_red"},
{.compatible = "fire,rgb_led_green"},
{.compatible = "fire,rgb_led_blue"},
{/* sentinel */}
};
static struct platform_driver led_pdrv = {
.probe = led_plat_probe,
.remove = led_plat_remove,
.driver = {
.name = "led_pdev",
.owner = THIS_MODULE,
.of_match_table = rgb_led,
},
.id_table = led_pdev_ids,
};
static int __init led_plat_drv_init(void)
{
printk("led platform driver init\n");
led_class = class_create(THIS_MODULE, "led_class");
if(IS_ERR(led_class))
{
printk(KERN_ERR" create class faild!/n");
return -EBUSY;
}
platform_driver_register(&led_pdrv);
return 0;
}
static void __exit led_plat_drv_exit(void)
{
printk("led platform driver exit\n");
platform_driver_unregister(&led_pdrv);
class_destroy(led_class);
}
module_init(led_plat_drv_init);
module_exit(led_plat_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liuchao");

27
examples/LICENSE Normal file
View File

@ -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.)

12
examples/Makefile Normal file
View File

@ -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

39
examples/include/lddbus.h Normal file
View File

@ -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 *);

39
examples/lddbus/Makefile Normal file
View File

@ -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

177
examples/lddbus/lddbus.c Normal file
View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

264
examples/misc-modules/jiq.c Normal file
View File

@ -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);

292
examples/misc-modules/jit.c Normal file
View File

@ -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);

View File

@ -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");

View File

@ -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");

109
examples/misc-modules/seq.c Normal file
View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

129
examples/misc-progs/inp.c Normal file
View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

136
examples/misc-progs/outp.c Normal file
View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

11
examples/pci/Makefile Normal file
View File

@ -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

63
examples/pci/pci_skel.c Normal file
View File

@ -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);

41
examples/sbull/Makefile Normal file
View File

@ -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

456
examples/sbull/sbull.c Normal file
View File

@ -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);

71
examples/sbull/sbull.h Normal file
View File

@ -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;

47
examples/sbull/sbull_load Normal file
View File

@ -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]*

View File

@ -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}

0
examples/scull/.depend Normal file
View File

230
examples/scull/.main.o.d Normal file
View File

@ -0,0 +1,230 @@
main.o: /home/chao/code/module/examples/scull/main.c \
include/linux/kconfig.h include/generated/autoconf.h \
include/linux/compiler_types.h include/linux/compiler_attributes.h \
include/linux/compiler-gcc.h include/linux/module.h include/linux/list.h \
include/linux/types.h include/uapi/linux/types.h \
arch/x86/include/generated/uapi/asm/types.h \
include/uapi/asm-generic/types.h include/asm-generic/int-ll64.h \
include/uapi/asm-generic/int-ll64.h \
arch/x86/include/uapi/asm/bitsperlong.h \
include/asm-generic/bitsperlong.h include/uapi/asm-generic/bitsperlong.h \
include/uapi/linux/posix_types.h include/linux/stddef.h \
include/uapi/linux/stddef.h include/linux/compiler_types.h \
arch/x86/include/asm/posix_types.h \
arch/x86/include/uapi/asm/posix_types_64.h \
include/uapi/asm-generic/posix_types.h include/linux/poison.h \
include/linux/const.h include/vdso/const.h include/uapi/linux/const.h \
include/linux/kernel.h include/linux/limits.h \
include/uapi/linux/limits.h include/vdso/limits.h \
include/linux/linkage.h include/linux/stringify.h include/linux/export.h \
include/linux/compiler.h arch/x86/include/asm/barrier.h \
arch/x86/include/asm/alternative.h arch/x86/include/asm/asm.h \
arch/x86/include/asm/nops.h include/asm-generic/barrier.h \
include/linux/kasan-checks.h include/linux/kcsan-checks.h \
arch/x86/include/asm/linkage.h include/linux/bitops.h \
include/linux/bits.h include/vdso/bits.h include/linux/build_bug.h \
arch/x86/include/asm/bitops.h arch/x86/include/asm/rmwcc.h \
include/asm-generic/bitops/find.h include/asm-generic/bitops/sched.h \
arch/x86/include/asm/arch_hweight.h arch/x86/include/asm/cpufeatures.h \
arch/x86/include/asm/required-features.h \
arch/x86/include/asm/disabled-features.h \
include/asm-generic/bitops/const_hweight.h \
include/asm-generic/bitops/instrumented-atomic.h \
include/linux/instrumented.h \
include/asm-generic/bitops/instrumented-non-atomic.h \
include/asm-generic/bitops/instrumented-lock.h \
include/asm-generic/bitops/le.h arch/x86/include/uapi/asm/byteorder.h \
include/linux/byteorder/little_endian.h \
include/uapi/linux/byteorder/little_endian.h include/linux/swab.h \
include/uapi/linux/swab.h arch/x86/include/uapi/asm/swab.h \
include/linux/byteorder/generic.h \
include/asm-generic/bitops/ext2-atomic-setbit.h include/linux/log2.h \
include/linux/typecheck.h include/linux/printk.h include/linux/init.h \
include/linux/kern_levels.h include/linux/cache.h \
include/uapi/linux/kernel.h include/uapi/linux/sysinfo.h \
arch/x86/include/asm/cache.h include/linux/dynamic_debug.h \
include/linux/jump_label.h arch/x86/include/asm/jump_label.h \
arch/x86/include/asm/div64.h include/asm-generic/div64.h \
include/linux/stat.h arch/x86/include/uapi/asm/stat.h \
include/uapi/linux/stat.h include/linux/time.h include/linux/seqlock.h \
include/linux/spinlock.h include/linux/preempt.h \
arch/x86/include/asm/preempt.h arch/x86/include/asm/percpu.h \
include/asm-generic/percpu.h include/linux/threads.h \
include/linux/percpu-defs.h include/linux/thread_info.h \
include/linux/bug.h arch/x86/include/asm/bug.h include/asm-generic/bug.h \
include/linux/restart_block.h include/linux/time64.h \
include/linux/math64.h include/vdso/math64.h include/vdso/time64.h \
include/uapi/linux/time.h include/uapi/linux/time_types.h \
arch/x86/include/asm/current.h arch/x86/include/asm/thread_info.h \
arch/x86/include/asm/page.h arch/x86/include/asm/page_types.h \
include/linux/mem_encrypt.h arch/x86/include/asm/mem_encrypt.h \
arch/x86/include/uapi/asm/bootparam.h include/linux/screen_info.h \
include/uapi/linux/screen_info.h include/linux/apm_bios.h \
include/uapi/linux/apm_bios.h include/uapi/linux/ioctl.h \
arch/x86/include/generated/uapi/asm/ioctl.h include/asm-generic/ioctl.h \
include/uapi/asm-generic/ioctl.h include/linux/edd.h \
include/uapi/linux/edd.h arch/x86/include/asm/ist.h \
arch/x86/include/uapi/asm/ist.h include/video/edid.h \
include/uapi/video/edid.h arch/x86/include/asm/page_64_types.h \
arch/x86/include/asm/kaslr.h arch/x86/include/asm/page_64.h \
include/linux/range.h include/asm-generic/memory_model.h \
include/linux/pfn.h include/asm-generic/getorder.h \
arch/x86/include/asm/cpufeature.h arch/x86/include/asm/processor.h \
arch/x86/include/asm/processor-flags.h \
arch/x86/include/uapi/asm/processor-flags.h \
arch/x86/include/asm/math_emu.h arch/x86/include/asm/ptrace.h \
arch/x86/include/asm/segment.h arch/x86/include/uapi/asm/ptrace.h \
arch/x86/include/uapi/asm/ptrace-abi.h \
arch/x86/include/asm/paravirt_types.h arch/x86/include/asm/desc_defs.h \
arch/x86/include/asm/kmap_types.h include/asm-generic/kmap_types.h \
arch/x86/include/asm/pgtable_types.h \
arch/x86/include/asm/pgtable_64_types.h arch/x86/include/asm/sparsemem.h \
arch/x86/include/asm/nospec-branch.h include/linux/static_key.h \
include/linux/frame.h arch/x86/include/asm/alternative-asm.h \
arch/x86/include/asm/msr-index.h arch/x86/include/asm/unwind_hints.h \
arch/x86/include/asm/orc_types.h arch/x86/include/asm/spinlock_types.h \
include/asm-generic/qspinlock_types.h \
include/asm-generic/qrwlock_types.h \
arch/x86/include/uapi/asm/sigcontext.h arch/x86/include/asm/msr.h \
arch/x86/include/asm/msr-index.h \
arch/x86/include/generated/uapi/asm/errno.h \
include/uapi/asm-generic/errno.h include/uapi/asm-generic/errno-base.h \
arch/x86/include/asm/cpumask.h include/linux/cpumask.h \
include/linux/bitmap.h include/linux/string.h \
include/uapi/linux/string.h arch/x86/include/asm/string.h \
arch/x86/include/asm/string_64.h include/linux/atomic.h \
arch/x86/include/asm/atomic.h arch/x86/include/asm/cmpxchg.h \
arch/x86/include/asm/cmpxchg_64.h arch/x86/include/asm/atomic64_64.h \
include/linux/atomic-arch-fallback.h \
include/asm-generic/atomic-instrumented.h \
include/asm-generic/atomic-long.h arch/x86/include/uapi/asm/msr.h \
include/linux/tracepoint-defs.h arch/x86/include/asm/paravirt.h \
arch/x86/include/asm/frame.h arch/x86/include/asm/special_insns.h \
include/linux/irqflags.h arch/x86/include/asm/irqflags.h \
arch/x86/include/asm/fpu/types.h arch/x86/include/asm/vmxfeatures.h \
arch/x86/include/asm/vdso/processor.h include/linux/personality.h \
include/uapi/linux/personality.h include/linux/err.h \
include/linux/bottom_half.h arch/x86/include/generated/asm/mmiowb.h \
include/asm-generic/mmiowb.h include/linux/spinlock_types.h \
include/linux/lockdep.h include/linux/rwlock_types.h \
arch/x86/include/asm/spinlock.h arch/x86/include/asm/qspinlock.h \
include/asm-generic/qspinlock.h arch/x86/include/asm/qrwlock.h \
include/asm-generic/qrwlock.h include/linux/rwlock.h \
include/linux/spinlock_api_smp.h include/linux/rwlock_api_smp.h \
include/linux/time32.h include/linux/timex.h include/uapi/linux/timex.h \
include/uapi/linux/param.h arch/x86/include/generated/uapi/asm/param.h \
include/asm-generic/param.h include/uapi/asm-generic/param.h \
arch/x86/include/asm/timex.h arch/x86/include/asm/tsc.h \
include/vdso/time32.h include/vdso/time.h include/linux/uidgid.h \
include/linux/highuid.h include/linux/kmod.h include/linux/umh.h \
include/linux/gfp.h include/linux/mmdebug.h include/linux/mmzone.h \
include/linux/wait.h include/uapi/linux/wait.h include/linux/numa.h \
include/linux/nodemask.h include/linux/pageblock-flags.h \
include/linux/page-flags-layout.h include/generated/bounds.h \
include/linux/mm_types.h include/linux/mm_types_task.h \
arch/x86/include/asm/tlbbatch.h include/linux/auxvec.h \
include/uapi/linux/auxvec.h arch/x86/include/uapi/asm/auxvec.h \
include/linux/rbtree.h include/linux/rcupdate.h include/linux/rcutree.h \
include/linux/rwsem.h include/linux/osq_lock.h \
include/linux/completion.h include/linux/swait.h include/linux/uprobes.h \
include/linux/errno.h include/uapi/linux/errno.h \
arch/x86/include/asm/uprobes.h include/linux/notifier.h \
include/linux/mutex.h include/linux/debug_locks.h include/linux/srcu.h \
include/linux/workqueue.h include/linux/timer.h include/linux/ktime.h \
include/linux/jiffies.h include/vdso/jiffies.h \
include/generated/timeconst.h include/vdso/ktime.h \
include/linux/timekeeping.h include/linux/timekeeping32.h \
include/linux/debugobjects.h include/linux/rcu_segcblist.h \
include/linux/srcutree.h include/linux/rcu_node_tree.h \
arch/x86/include/asm/mmu.h include/linux/page-flags.h \
include/linux/memory_hotplug.h arch/x86/include/asm/mmzone.h \
arch/x86/include/asm/mmzone_64.h arch/x86/include/asm/smp.h \
arch/x86/include/asm/mpspec.h arch/x86/include/asm/mpspec_def.h \
arch/x86/include/asm/x86_init.h arch/x86/include/asm/apicdef.h \
arch/x86/include/asm/apic.h arch/x86/include/asm/fixmap.h \
arch/x86/include/asm/acpi.h include/acpi/pdc_intel.h \
arch/x86/include/asm/numa.h arch/x86/include/asm/topology.h \
include/asm-generic/topology.h arch/x86/include/uapi/asm/vsyscall.h \
include/asm-generic/fixmap.h arch/x86/include/asm/hardirq.h \
arch/x86/include/asm/io_apic.h arch/x86/include/asm/irq_vectors.h \
include/linux/topology.h include/linux/arch_topology.h \
include/linux/percpu.h include/linux/smp.h include/linux/smp_types.h \
include/linux/llist.h include/linux/sysctl.h include/uapi/linux/sysctl.h \
include/linux/elf.h arch/x86/include/asm/elf.h \
arch/x86/include/asm/user.h arch/x86/include/asm/user_64.h \
arch/x86/include/asm/fsgsbase.h arch/x86/include/asm/vdso.h \
include/uapi/linux/elf.h include/uapi/linux/elf-em.h \
include/linux/kobject.h include/linux/sysfs.h include/linux/kernfs.h \
include/linux/idr.h include/linux/radix-tree.h include/linux/xarray.h \
include/linux/kconfig.h include/linux/kobject_ns.h include/linux/kref.h \
include/linux/refcount.h include/linux/moduleparam.h \
include/linux/rbtree_latch.h include/linux/error-injection.h \
include/asm-generic/error-injection.h arch/x86/include/asm/module.h \
include/asm-generic/module.h arch/x86/include/asm/orc_types.h \
include/linux/slab.h include/linux/overflow.h \
include/linux/percpu-refcount.h include/linux/kasan.h include/linux/fs.h \
include/linux/wait_bit.h include/linux/kdev_t.h \
include/uapi/linux/kdev_t.h include/linux/dcache.h \
include/linux/rculist.h include/linux/rculist_bl.h \
include/linux/list_bl.h include/linux/bit_spinlock.h \
include/linux/lockref.h include/linux/stringhash.h include/linux/hash.h \
include/linux/path.h include/linux/list_lru.h include/linux/shrinker.h \
include/linux/pid.h include/linux/capability.h \
include/uapi/linux/capability.h include/linux/semaphore.h \
include/linux/fcntl.h include/uapi/linux/fcntl.h \
arch/x86/include/generated/uapi/asm/fcntl.h \
include/uapi/asm-generic/fcntl.h include/uapi/linux/openat2.h \
include/linux/migrate_mode.h include/linux/percpu-rwsem.h \
include/linux/rcuwait.h include/linux/sched/signal.h \
include/linux/signal.h include/linux/signal_types.h \
include/uapi/linux/signal.h arch/x86/include/asm/signal.h \
arch/x86/include/uapi/asm/signal.h \
include/uapi/asm-generic/signal-defs.h \
arch/x86/include/uapi/asm/siginfo.h include/uapi/asm-generic/siginfo.h \
include/linux/sched.h include/uapi/linux/sched.h include/linux/sem.h \
include/uapi/linux/sem.h include/linux/ipc.h \
include/linux/rhashtable-types.h include/uapi/linux/ipc.h \
arch/x86/include/generated/uapi/asm/ipcbuf.h \
include/uapi/asm-generic/ipcbuf.h arch/x86/include/uapi/asm/sembuf.h \
include/linux/shm.h include/uapi/linux/shm.h \
include/uapi/asm-generic/hugetlb_encode.h \
arch/x86/include/uapi/asm/shmbuf.h include/uapi/asm-generic/shmbuf.h \
arch/x86/include/asm/shmparam.h include/linux/kcov.h \
include/uapi/linux/kcov.h include/linux/plist.h include/linux/hrtimer.h \
include/linux/hrtimer_defs.h include/linux/timerqueue.h \
include/linux/seccomp.h include/uapi/linux/seccomp.h \
arch/x86/include/asm/seccomp.h arch/x86/include/asm/unistd.h \
arch/x86/include/uapi/asm/unistd.h \
arch/x86/include/generated/uapi/asm/unistd_64.h \
arch/x86/include/generated/asm/unistd_64_x32.h \
arch/x86/include/generated/asm/unistd_32_ia32.h \
arch/x86/include/asm/ia32_unistd.h include/asm-generic/seccomp.h \
include/uapi/linux/unistd.h include/linux/resource.h \
include/uapi/linux/resource.h \
arch/x86/include/generated/uapi/asm/resource.h \
include/asm-generic/resource.h include/uapi/asm-generic/resource.h \
include/linux/latencytop.h include/linux/sched/prio.h \
include/linux/sched/types.h include/linux/task_io_accounting.h \
include/linux/posix-timers.h include/linux/alarmtimer.h \
include/uapi/linux/rseq.h include/linux/kcsan.h \
include/linux/sched/jobctl.h include/linux/sched/task.h \
include/linux/uaccess.h arch/x86/include/asm/uaccess.h \
arch/x86/include/asm/smap.h arch/x86/include/asm/extable.h \
arch/x86/include/asm/uaccess_64.h include/linux/cred.h \
include/linux/key.h include/linux/assoc_array.h \
include/linux/sched/user.h include/linux/ratelimit.h \
include/linux/rcu_sync.h include/linux/delayed_call.h \
include/linux/uuid.h include/uapi/linux/uuid.h include/linux/errseq.h \
include/linux/ioprio.h include/linux/sched/rt.h \
include/linux/iocontext.h include/linux/fs_types.h \
include/uapi/linux/fs.h include/linux/quota.h \
include/linux/percpu_counter.h include/uapi/linux/dqblk_xfs.h \
include/linux/dqblk_v1.h include/linux/dqblk_v2.h \
include/linux/dqblk_qtree.h include/linux/projid.h \
include/uapi/linux/quota.h include/linux/nfs_fs_i.h \
include/linux/proc_fs.h include/linux/seq_file.h include/linux/cdev.h \
include/linux/device.h include/linux/dev_printk.h include/linux/ioport.h \
include/linux/klist.h include/linux/pm.h include/linux/device/bus.h \
include/linux/device/class.h include/linux/device/driver.h \
arch/x86/include/asm/device.h include/linux/pm_wakeup.h \
/home/chao/code/module/examples/scull/scull.h

View File

@ -0,0 +1,35 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"compilerPath": "/usr/bin/gcc",
"cStandard": "gnu11",
"cppStandard": "gnu++14",
"intelliSenseMode": "clang-x64"
},
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"compilerPath": "/usr/bin/gcc",
"cStandard": "gnu11",
"cppStandard": "gnu++14",
"intelliSenseMode": "gcc-x64"
}
],
"version": 4
}

3
examples/scull/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"C_Cpp.errorSquiggles": "Disabled"
}

43
examples/scull/Makefile Normal file
View File

@ -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
EXTRA_CFLAGS += $(DEBFLAGS)
EXTRA_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) $(EXTRA_CFLAGS) -M *.c > .depend
ifeq (.depend,$(wildcard .depend))
include .depend
endif

View File

417
examples/scull/access.c Normal file
View File

@ -0,0 +1,417 @@
/*
* 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 <linux/sched/signal.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,
.unlocked_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 DEFINE_SPINLOCK(scull_u_lock);
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->cred->uid.val) && /* allow user */
(scull_u_owner != current->cred->euid.val) && /* 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->cred->uid.val; /* 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,
.unlocked_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 DEFINE_SPINLOCK(scull_w_lock);
static inline int scull_w_available(void)
{
return scull_w_count == 0 ||
scull_w_owner == current->cred->uid.val ||
scull_w_owner == current->cred->euid.val ||
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->cred->uid.val; /* 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,
.unlocked_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;
static DEFINE_SPINLOCK(scull_c_lock);
/* 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));
sema_init(&(lptr->device.sem),1);
/* 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,
.unlocked_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);
sema_init(&dev->sem,1);
/* 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;
}

678
examples/scull/main.c Normal file
View File

@ -0,0 +1,678 @@
/*
* 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 <linux/slab.h>
//#include <asm/uaccess.h> /* copy_*_user */
#include <linux/uaccess.h>
#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);
entry = proc_create("scullseq", 0, NULL,&scull_proc_ops);
//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
*/
long scull_ioctl(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,
.unlocked_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);
sema_init(&scull_devices[i].sem,1);
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);

View File

@ -0,0 +1 @@
kernel//mnt/data/code/learn_linux_drive/examples/scull/scull.ko

402
examples/scull/pipe.c Normal file
View File

@ -0,0 +1,402 @@
/*
* 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 <linux/signal.h>
#if 0
#include <linux/sched/signal.h>
#endif
#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,
.unlocked_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);
sema_init(&scull_p_devices[i].sem,1);
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 */
}

177
examples/scull/scull.h Normal file
View File

@ -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);
long scull_ioctl(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_ */

142
examples/scull/scull.init Normal file
View File

@ -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

66
examples/scull/scull_load Normal file
View File

@ -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

View File

@ -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

46
examples/scullc/Makefile Normal file
View File

@ -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

600
examples/scullc/main.c Normal file
View File

@ -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);

118
examples/scullc/mmap.c Normal file
View File

@ -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;
}

122
examples/scullc/scullc.h Normal file
View File

@ -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

View File

@ -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]

View File

@ -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

46
examples/sculld/Makefile Normal file
View File

@ -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

632
examples/sculld/main.c Normal file
View File

@ -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);

118
examples/sculld/mmap.c Normal file
View File

@ -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;
}

126
examples/sculld/sculld.h Normal file
View File

@ -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

View File

@ -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]

View File

@ -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

46
examples/scullp/Makefile Normal file
View File

@ -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

598
examples/scullp/main.c Normal file
View File

@ -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);

119
examples/scullp/mmap.c Normal file
View File

@ -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;
}

122
examples/scullp/scullp.h Normal file
View File

@ -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

View File

@ -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]

View File

@ -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

46
examples/scullv/Makefile Normal file
View File

@ -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

597
examples/scullv/main.c Normal file
View File

@ -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);

120
examples/scullv/mmap.c Normal file
View File

@ -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;
}

122
examples/scullv/scullv.h Normal file
View File

@ -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

View File

@ -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]

View File

@ -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

40
examples/short/Makefile Normal file
View File

@ -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

692
examples/short/short.c Normal file
View File

@ -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);

61
examples/short/short_load Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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}

View File

@ -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}

39
examples/simple/Makefile Normal file
View File

@ -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

235
examples/simple/simple.c Normal file
View File

@ -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);

View File

@ -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]

View File

@ -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]

1
examples/skull/Makefile Normal file
View File

@ -0,0 +1 @@
foo:

View File

@ -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);

200
examples/skull/skull_init.c Normal file
View File

@ -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);

Some files were not shown because too many files have changed in this diff Show More