first commit
This commit is contained in:
commit
d98782fd8b
30
device_model/Makefile
Normal file
30
device_model/Makefile
Normal 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
65
device_model/my_bus.c
Normal 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
70
device_model/my_device.c
Normal 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
69
device_model/my_driver.c
Normal 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
11
device_model/pub.h
Normal 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
30
device_tree/Makefile
Normal 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
317
device_tree/dt_plat_drv.c
Normal 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
27
examples/LICENSE
Normal 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
12
examples/Makefile
Normal 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
39
examples/include/lddbus.h
Normal 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
39
examples/lddbus/Makefile
Normal 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
177
examples/lddbus/lddbus.c
Normal 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);
|
32
examples/misc-modules/Makefile
Normal file
32
examples/misc-modules/Makefile
Normal 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
|
||||
|
||||
|
81
examples/misc-modules/complete.c
Normal file
81
examples/misc-modules/complete.c
Normal 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);
|
||||
|
89
examples/misc-modules/faulty.c
Normal file
89
examples/misc-modules/faulty.c
Normal 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);
|
||||
|
20
examples/misc-modules/hello.c
Normal file
20
examples/misc-modules/hello.c
Normal 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);
|
40
examples/misc-modules/hellop.c
Normal file
40
examples/misc-modules/hellop.c
Normal 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
264
examples/misc-modules/jiq.c
Normal 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
292
examples/misc-modules/jit.c
Normal 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);
|
69
examples/misc-modules/kdataalign.c
Normal file
69
examples/misc-modules/kdataalign.c
Normal 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");
|
48
examples/misc-modules/kdatasize.c
Normal file
48
examples/misc-modules/kdatasize.c
Normal 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
109
examples/misc-modules/seq.c
Normal 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);
|
294
examples/misc-modules/silly.c
Normal file
294
examples/misc-modules/silly.c
Normal 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);
|
84
examples/misc-modules/sleepy.c
Normal file
84
examples/misc-modules/sleepy.c
Normal 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);
|
||||
|
13
examples/misc-progs/Makefile
Normal file
13
examples/misc-progs/Makefile
Normal 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
|
||||
|
57
examples/misc-progs/asynctest.c
Normal file
57
examples/misc-progs/asynctest.c
Normal 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;
|
||||
}
|
||||
}
|
58
examples/misc-progs/dataalign.c
Normal file
58
examples/misc-progs/dataalign.c
Normal 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;
|
||||
}
|
35
examples/misc-progs/datasize.c
Normal file
35
examples/misc-progs/datasize.c
Normal 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;
|
||||
}
|
19
examples/misc-progs/gdbline
Normal file
19
examples/misc-progs/gdbline
Normal 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
129
examples/misc-progs/inp.c
Normal 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);
|
||||
}
|
||||
|
38
examples/misc-progs/load50.c
Normal file
38
examples/misc-progs/load50.c
Normal 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;
|
||||
}
|
||||
|
87
examples/misc-progs/mapcmp.c
Normal file
87
examples/misc-progs/mapcmp.c
Normal 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);
|
||||
}
|
71
examples/misc-progs/mapper.c
Normal file
71
examples/misc-progs/mapper.c
Normal 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;
|
||||
}
|
||||
|
44
examples/misc-progs/nbtest.c
Normal file
44
examples/misc-progs/nbtest.c
Normal 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);
|
||||
}
|
84
examples/misc-progs/netifdebug.c
Normal file
84
examples/misc-progs/netifdebug.c
Normal 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
136
examples/misc-progs/outp.c
Normal 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);
|
||||
}
|
47
examples/misc-progs/polltest.c
Normal file
47
examples/misc-progs/polltest.c
Normal 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);
|
||||
}
|
42
examples/misc-progs/setconsole.c
Normal file
42
examples/misc-progs/setconsole.c
Normal 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);
|
||||
}
|
47
examples/misc-progs/setlevel.c
Normal file
47
examples/misc-progs/setlevel.c
Normal 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
11
examples/pci/Makefile
Normal 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
63
examples/pci/pci_skel.c
Normal 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
41
examples/sbull/Makefile
Normal 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
456
examples/sbull/sbull.c
Normal 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
71
examples/sbull/sbull.h
Normal 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
47
examples/sbull/sbull_load
Normal 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]*
|
14
examples/sbull/sbull_unload
Normal file
14
examples/sbull/sbull_unload
Normal 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
0
examples/scull/.depend
Normal file
230
examples/scull/.main.o.d
Normal file
230
examples/scull/.main.o.d
Normal 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
|
35
examples/scull/.vscode/c_cpp_properties.json
vendored
Normal file
35
examples/scull/.vscode/c_cpp_properties.json
vendored
Normal 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
3
examples/scull/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"C_Cpp.errorSquiggles": "Disabled"
|
||||
}
|
43
examples/scull/Makefile
Normal file
43
examples/scull/Makefile
Normal 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
|
0
examples/scull/Module.symvers
Normal file
0
examples/scull/Module.symvers
Normal file
417
examples/scull/access.c
Normal file
417
examples/scull/access.c
Normal 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
678
examples/scull/main.c
Normal 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);
|
1
examples/scull/modules.order
Normal file
1
examples/scull/modules.order
Normal file
@ -0,0 +1 @@
|
||||
kernel//mnt/data/code/learn_linux_drive/examples/scull/scull.ko
|
402
examples/scull/pipe.c
Normal file
402
examples/scull/pipe.c
Normal 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
177
examples/scull/scull.h
Normal 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
142
examples/scull/scull.init
Normal 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
66
examples/scull/scull_load
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
20
examples/scull/scull_unload
Normal file
20
examples/scull/scull_unload
Normal 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
46
examples/scullc/Makefile
Normal 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
600
examples/scullc/main.c
Normal 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
118
examples/scullc/mmap.c
Normal 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
122
examples/scullc/scullc.h
Normal 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
|
||||
|
||||
|
||||
|
30
examples/scullc/scullc_load
Normal file
30
examples/scullc/scullc_load
Normal 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]
|
11
examples/scullc/scullc_unload
Normal file
11
examples/scullc/scullc_unload
Normal 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
46
examples/sculld/Makefile
Normal 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
632
examples/sculld/main.c
Normal 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
118
examples/sculld/mmap.c
Normal 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
126
examples/sculld/sculld.h
Normal 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
|
||||
|
||||
|
||||
|
30
examples/sculld/sculld_load
Normal file
30
examples/sculld/sculld_load
Normal 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]
|
11
examples/sculld/sculld_unload
Normal file
11
examples/sculld/sculld_unload
Normal 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
46
examples/scullp/Makefile
Normal 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
598
examples/scullp/main.c
Normal 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
119
examples/scullp/mmap.c
Normal 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
122
examples/scullp/scullp.h
Normal 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
|
||||
|
||||
|
||||
|
30
examples/scullp/scullp_load
Normal file
30
examples/scullp/scullp_load
Normal 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]
|
11
examples/scullp/scullp_unload
Normal file
11
examples/scullp/scullp_unload
Normal 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
46
examples/scullv/Makefile
Normal 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
597
examples/scullv/main.c
Normal 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
120
examples/scullv/mmap.c
Normal 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
122
examples/scullv/scullv.h
Normal 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
|
||||
|
||||
|
||||
|
30
examples/scullv/scullv_load
Normal file
30
examples/scullv/scullv_load
Normal 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]
|
11
examples/scullv/scullv_unload
Normal file
11
examples/scullv/scullv_unload
Normal 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
40
examples/short/Makefile
Normal 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
692
examples/short/short.c
Normal 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
61
examples/short/short_load
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
16
examples/short/short_unload
Normal file
16
examples/short/short_unload
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
31
examples/shortprint/Makefile
Normal file
31
examples/shortprint/Makefile
Normal 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
|
521
examples/shortprint/shortprint.c
Normal file
521
examples/shortprint/shortprint.c
Normal 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);
|
46
examples/shortprint/shortprint.h
Normal file
46
examples/shortprint/shortprint.h
Normal 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
|
31
examples/shortprint/shortprint_load
Normal file
31
examples/shortprint/shortprint_load
Normal 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}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
15
examples/shortprint/shortprint_unload
Normal file
15
examples/shortprint/shortprint_unload
Normal 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
39
examples/simple/Makefile
Normal 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
235
examples/simple/simple.c
Normal 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);
|
26
examples/simple/simple_load
Normal file
26
examples/simple/simple_load
Normal 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]
|
14
examples/simple/simple_unload
Normal file
14
examples/simple/simple_unload
Normal 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
1
examples/skull/Makefile
Normal file
@ -0,0 +1 @@
|
||||
foo:
|
22
examples/skull/skull_clean.c
Normal file
22
examples/skull/skull_clean.c
Normal 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
200
examples/skull/skull_init.c
Normal 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
Loading…
x
Reference in New Issue
Block a user