summaryrefslogtreecommitdiffstats
path: root/misc-modules/silly.c
diff options
context:
space:
mode:
authorJavier Martinez Canillas <martinez.javier@gmail.com>2010-11-27 07:49:17 +0100
committerJavier Martinez Canillas <martinez.javier@gmail.com>2010-11-27 07:49:17 +0100
commitab121f379a3cff458c90e6f480ba4bb68c8733dd (patch)
treea9851af109ee83646d108bc247d03b131461b764 /misc-modules/silly.c
downloadldd3-ab121f379a3cff458c90e6f480ba4bb68c8733dd.tar.gz
Linux Device Drivers 3 examples
Diffstat (limited to 'misc-modules/silly.c')
-rw-r--r--misc-modules/silly.c294
1 files changed, 294 insertions, 0 deletions
diff --git a/misc-modules/silly.c b/misc-modules/silly.c
new file mode 100644
index 0000000..3b1f893
--- /dev/null
+++ b/misc-modules/silly.c
@@ -0,0 +1,294 @@
+/*
+ * silly.c -- Simple Tool for Unloading and Printing ISA Data
+ *
+ * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
+ * Copyright (C) 2001 O'Reilly & Associates
+ *
+ * The source code in this file can be freely used, adapted,
+ * and redistributed in source or binary form, so long as an
+ * acknowledgment appears in derived source files. The citation
+ * should list that the code comes from the book "Linux Device
+ * Drivers" by Alessandro Rubini and Jonathan Corbet, published
+ * by O'Reilly & Associates. No warranty is attached;
+ * we cannot take responsibility for errors or fitness for use.
+ *
+ * $Id: silly.c,v 1.3 2004/09/26 07:02:43 gregkh Exp $
+ */
+
+/* =========================> BIG FAT WARNING:
+ * This will only work on architectures with an ISA memory range.
+ * It won't work on other computers.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+
+#include <linux/sched.h>
+#include <linux/kernel.h> /* printk() */
+#include <linux/fs.h> /* everything... */
+#include <linux/errno.h> /* error codes */
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/poll.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+int silly_major = 0;
+module_param(silly_major, int, 0);
+MODULE_AUTHOR("Alessandro Rubini");
+MODULE_LICENSE("Dual BSD/GPL");
+
+/*
+ * The devices access the 640k-1M memory.
+ * minor 0 uses ioread8/iowrite8
+ * minor 1 uses ioread16/iowrite16
+ * minor 2 uses ioread32/iowrite32
+ * minor 3 uses memcpy_fromio()/memcpy_toio()
+ */
+
+/*
+ * Here's our address range, and a place to store the ioremap'd base.
+ */
+#define ISA_BASE 0xA0000
+#define ISA_MAX 0x100000 /* for general memory access */
+
+#define VIDEO_MAX 0xC0000 /* for vga access */
+#define VGA_BASE 0xb8000
+static void __iomem *io_base;
+
+
+
+int silly_open(struct inode *inode, struct file *filp)
+{
+ return 0;
+}
+
+int silly_release(struct inode *inode, struct file *filp)
+{
+ return 0;
+}
+
+enum silly_modes {M_8=0, M_16, M_32, M_memcpy};
+
+ssize_t silly_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
+{
+ int retval;
+ int mode = iminor(filp->f_dentry->d_inode);
+ void __iomem *add;
+ unsigned long isa_addr = ISA_BASE + *f_pos;
+ unsigned char *kbuf, *ptr;
+
+ if (isa_addr + count > ISA_MAX) /* range: 0xA0000-0x100000 */
+ count = ISA_MAX - isa_addr;
+
+ /*
+ * too big an f_pos (caused by a malicious lseek())
+ * would result in a negative count
+ */
+ if (count < 0)
+ return 0;
+
+ kbuf = kmalloc(count, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+ ptr = kbuf;
+ retval = count;
+ /*
+ * Convert our address into our remapped area.
+ */
+ add = (void __iomem *)(io_base + (isa_addr - ISA_BASE));
+ /*
+ * kbuf is aligned, but the reads might not. In order not to
+ * drive me mad with unaligned leading and trailing bytes,
+ * I downgrade the `mode' if unaligned xfers are requested.
+ */
+
+ if (mode == M_32 && ((isa_addr | count) & 3))
+ mode = M_16;
+ if (mode == M_16 && ((isa_addr | count) & 1))
+ mode = M_8;
+
+ switch(mode) {
+ case M_32:
+ while (count >= 4) {
+ *(u32 *)ptr = ioread32(add);
+ add += 4;
+ count -= 4;
+ ptr += 4;
+ }
+ break;
+
+ case M_16:
+ while (count >= 2) {
+ *(u16 *)ptr = ioread16(add);
+ add+=2;
+ count-=2;
+ ptr+=2;
+ }
+ break;
+
+ case M_8:
+ while (count) {
+ *ptr = ioread8(add);
+ add++;
+ count--;
+ ptr++;
+ }
+ break;
+
+ case M_memcpy:
+ memcpy_fromio(ptr, add, count);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ if ((retval > 0) && copy_to_user(buf, kbuf, retval))
+ retval = -EFAULT;
+ kfree(kbuf);
+ *f_pos += retval;
+ return retval;
+}
+
+
+ssize_t silly_write(struct file *filp, const char __user *buf, size_t count,
+ loff_t *f_pos)
+{
+ int retval;
+ int mode = iminor(filp->f_dentry->d_inode);
+ unsigned long isa_addr = ISA_BASE + *f_pos;
+ unsigned char *kbuf, *ptr;
+ void __iomem *add;
+
+ /*
+ * Writing is dangerous.
+ * Allow root-only, independently of device permissions
+ */
+ if (!capable(CAP_SYS_RAWIO))
+ return -EPERM;
+
+ if (isa_addr + count > ISA_MAX) /* range: 0xA0000-0x100000 */
+ count = ISA_MAX - isa_addr;
+
+ /*
+ * too big an f_pos (caused by a malicious lseek())
+ * results in a negative count
+ */
+ if (count < 0)
+ return 0;
+
+ kbuf = kmalloc(count, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+ ptr = kbuf;
+ retval=count;
+
+ /*
+ * kbuf is aligned, but the writes might not. In order not to
+ * drive me mad with unaligned leading and trailing bytes,
+ * I downgrade the `mode' if unaligned xfers are requested.
+ */
+
+ if (mode == M_32 && ((isa_addr | count) & 3))
+ mode = M_16;
+ if (mode == M_16 && ((isa_addr | count) & 1))
+ mode = M_8;
+
+ if (copy_from_user(kbuf, buf, count)) {
+ kfree(kbuf);
+ return -EFAULT;
+ }
+ ptr = kbuf;
+
+ /*
+ * Switch over to our remapped address space.
+ */
+ add = (void __iomem *)(io_base + (isa_addr - ISA_BASE));
+
+ switch(mode) {
+ case M_32:
+ while (count >= 4) {
+ iowrite8(*(u32 *)ptr, add);
+ add += 4;
+ count -= 4;
+ ptr += 4;
+ }
+ break;
+
+ case M_16:
+ while (count >= 2) {
+ iowrite8(*(u16 *)ptr, add);
+ add += 2;
+ count -= 2;
+ ptr += 2;
+ }
+ break;
+
+ case M_8:
+ while (count) {
+ iowrite8(*ptr, add);
+ add++;
+ count--;
+ ptr++;
+ }
+ break;
+
+ case M_memcpy:
+ memcpy_toio(add, ptr, count);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ *f_pos += retval;
+ kfree(kbuf);
+ return retval;
+}
+
+
+unsigned int silly_poll(struct file *filp, poll_table *wait)
+{
+ return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
+}
+
+
+struct file_operations silly_fops = {
+ .read = silly_read,
+ .write = silly_write,
+ .poll = silly_poll,
+ .open = silly_open,
+ .release = silly_release,
+ .owner = THIS_MODULE
+};
+
+int silly_init(void)
+{
+ int result = register_chrdev(silly_major, "silly", &silly_fops);
+ if (result < 0) {
+ printk(KERN_INFO "silly: can't get major number\n");
+ return result;
+ }
+ if (silly_major == 0)
+ silly_major = result; /* dynamic */
+ /*
+ * Set up our I/O range.
+ */
+
+ /* this line appears in silly_init */
+ io_base = ioremap(ISA_BASE, ISA_MAX - ISA_BASE);
+ return 0;
+}
+
+void silly_cleanup(void)
+{
+ iounmap(io_base);
+ unregister_chrdev(silly_major, "silly");
+}
+
+
+module_init(silly_init);
+module_exit(silly_cleanup);