Hi all,
I have written some very basic kernel modules. If it interests someone, i attach it.
One is a hello device drviver and the other shows some mm tricks.
It's very well annoted I think :)
/* * tyler@xxxxxxxx * * Hello World Driver (intended to Linux Kernel 2.6(.11)) * * Driver for the hello device. This driver is intended to show * and teach some basic kernel mechanics : * _ Linux Kernel Module programming * _ Driver programming * _ Procfs operations * _ Input/Output control * _ Semaphores Locking * _ Wait queues * * All that in a simp;e module. This module isn't very useful and I don't * if someone will be interested. * However, to test it, you have to : * _ compile the module (I don't provide the Makefile, do it yourself :p) * _ load it * _ at the module loading, you will see a message : * "Major number X has been assigned to device hello" * If you don't see it, have a look in the file /var/log/messages * You now have to create the device node : * mknod /dev/hello c X 0 * _ you can now test the device driver and the hello device !:) */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <linux/mm.h> #include <linux/slab.h> #include <linux/devfs_fs_kernel.h> #include <asm/semaphore.h> #include <linux/poll.h> #include <linux/errno.h> #include <linux/proc_fs.h> #define DRIVER_AUTHOR "tyler@xxxxxxxx" #define DRIVER_DESC "Hello World Driver" #define DEVICE_NAME "hello" int loading(void) ; void unloading(void) ; static int hello_open(struct inode *, struct file *) ; static int hello_release(struct inode *, struct file *) ; static ssize_t hello_read(struct inode *, char *, size_t, loff_t) ; static ssize_t hello_write(struct inode *, const char *, size_t, loff_t) ; static int hello_ioctl(struct inode *, struct file *, unsigned int , unsigned long); static unsigned int hello_poll(struct file *, poll_table *) ; static loff_t hello_llseek(struct file *, loff_t) ; static int proc_hello_read(char *,char **,off_t,int,int *,void *) ; static int proc_hello_write(struct file *,const char *,unsigned long,void *) ; static struct file_operations fops = { read : hello_read, write : hello_write, open : hello_open, release : hello_release, ioctl : hello_ioctl, poll : hello_poll, llseek : hello_llseek, }; static int Major=0 ; static int count=0 ; static char *msg ; static char *msgPtr ; static struct semaphore sem ; static wait_queue_head_t attente ; static wait_queue_head_t inq ; static struct proc_dir_entry *hello_dir ; static struct proc_dir_entry *hello ; /* * Loading function */ int loading(void) { Major = register_chrdev(Major,DEVICE_NAME,&fops) ; if (Major<0) { printk(KERN_ERR "Cannot allocate a major number.\n"); return Major ; } printk("<1>Major number %d has been assigned to device %s.\n",Major,DEVICE_NAME); sema_init(&sem,1) ; init_waitqueue_head(&attente) ; init_waitqueue_head(&inq) ; hello_dir = proc_mkdir("hello",NULL) ; if(hello_dir==NULL){ unregister_chrdev(Major,DEVICE_NAME) ; return -ENOMEM ; } hello = create_proc_entry("hello",0644,hello_dir) ; if(hello==NULL){ remove_proc_entry("hello",NULL) ; unregister_chrdev(Major,DEVICE_NAME) ; return -ENOMEM ; } hello->read_proc = proc_hello_read ; hello->write_proc = proc_hello_write ; return 0 ; } /* * Unloading function */ void unloading(void) { int ret=0 ; ret = unregister_chrdev(Major,DEVICE_NAME) ; if(ret<0){ printk("<1>unregister_chrdev() error.\n") ; } remove_proc_entry("hello",NULL) ; remove_proc_entry("hello",hello_dir) ; } /* * Read acces to the file /proc/hello */ int proc_hello_read(char *page, char **start, off_t off, int count, int *eof, void *data) { int len ; down_interruptible(&sem) ; len = sprintf(page,"%s",msg) ; up(&sem) ; return len ; } /* * Write acces to the file /proc/hello */ static int proc_hello_write(struct file *file, const char *buffer, unsigned long count, void *data) { char *bufPtr = buffer ; int len = 0 ; char *msgPtr = msg ; down_interruptible(&sem) ; if(msg){ kfree(msg) ; msg=(char *)kmalloc((count+1)*sizeof(char),GFP_KERNEL) ; } while(count && *bufPtr){ get_user(*(msgPtr++),bufPtr++) ; count-- ; len++ ; } up(&sem) ; return len ; } /* * Hello open function */ static int hello_open(struct inode *inode, struct file *file) { down_interruptible(&sem) ; if(count==0){ msg=(char *)kmalloc(9*sizeof(char),GFP_KERNEL) ; sprintf(msg,"Nothing\n") ; } count++ ; up(&sem) ; msgPtr=msg ; return 0 ; } /* * Hello close function */ static int hello_release(struct inode *inode, struct file *file) { return 0 ; } /* * Hello read function : * returns the current string stored in the device * if there is no data in the device, the funcion blocks until there is some data * written */ static ssize_t hello_read(struct inode *inode, char *buf, size_t len, loff_t offset) { int bytes=0 ; int minor = MINOR(inode->i_rdev) ; printk(KERN_ERR "*************************************************%d**",minor) ; down_interruptible(&sem) ; while(strncmp(msg,"Nothing\n",8)==0){ up(&sem) ; printk("<1> Sleep on\n") ; interruptible_sleep_on(&attente) ; down_interruptible(&sem) ; } while(len && *msgPtr){ put_user(*(msgPtr++),buf++) ; len-- ; bytes++ ; } up(&sem) ; return bytes ; } /* * Hello write function * Change the string stored in the hello device and wakes up * the eventual sleeping read's */ static ssize_t hello_write(struct inode *inode, const char *buf, size_t len, loff_t offset) { int bytes=0 ; const char *bufPtr = buf ; char *msgPtr = msg ; down_interruptible(&sem) ; if(msg){ kfree(msg) ; msg=(char *)kmalloc((len+1)*sizeof(char),GFP_KERNEL) ; } while(len && *bufPtr){ get_user(*(msgPtr++),bufPtr++) ; len-- ; bytes++ ; } *msgPtr='\0' ; up(&sem) ; wake_up_interruptible(&attente) ; return bytes ; } /* * Implementation of an IOCTL to reset the device */ static int hello_ioctl(struct inode *inode, struct file *filp, unsigned int ioctl_num, unsigned long ioctl_param) { switch(ioctl_num){ case 0 :/* Reseting the device */ down_interruptible(&sem) ; if(msg){ kfree(msg) ; msg=(char *)kmalloc(9*sizeof(char),GFP_KERNEL) ; sprintf(msg,"Nothing\n") ; } up(&sem) ; break ; default: printk(KERN_ERR "Erreur : ioctl non implemente.\n"); return -ENODEV ; break ; } return 0 ; } /* * Polling on the hello device */ static unsigned int hello_poll(struct file *filp, poll_table *wait) { unsigned int mask = 0 ; poll_wait(filp,&inq,wait) ; if(strncmp(msg,"Nothing\n",8)!=0) mask |= POLLIN | POLLRDNORM ; mask |= POLLOUT | POLLWRNORM ; return mask ; } /* * Seek the device */ static loff_t hello_llseek(struct file *filp, loff_t off) { loff_t new_pos ; new_pos = filp->f_pos + off ; if(new_pos<0) return -EINVAL ; return new_pos ; } module_init(loading) ; module_exit(unloading) ; MODULE_LICENSE("GPL") ; MODULE_AUTHOR(DRIVER_AUTHOR) ; MODULE_DESCRIPTION(DRIVER_DESC) ;
#include <linux/kernel.h> #include <linux/fs.h> #include <linux/module.h> #include <linux/mm.h> #include <linux/slab.h> #include <linux/vmalloc.h> unsigned long new_page ; unsigned long temp ; struct page *new_desc_page ; int init_module() { new_page = __get_free_page(GFP_KERNEL) ; new_desc_page = (struct page *)virt_to_page(new_page) ; /* * That's how the virtual memory system works. * To retrieve the virtual adress corresponding to the page, * we get the index in the page frame (new_desc_page-mem_map). * We then multiply it by the size of the page. We get the physical adress. * And in kernel mode, the differences between virtual and physical adresses * is just an offset of PAGE_OFFSET. So we add PAGE_OFFSET */ temp = (new_desc_page-mem_map)*PAGE_SIZE+PAGE_OFFSET ; printk("0x%x\n",temp) ; printk("0x%x\n",new_page) ; /* * In kernel mode, the virtual adresses has just an offset of PAGE_OFFSET * (the page tables are properly configured to do this) * On i386, the PAGE_OFFSET=0xC0000000 */ temp = __pa(new_page) ; printk("0x%x\n",new_page-PAGE_OFFSET) ; printk("0x%x\n",temp) ; return 0 ; } void cleanup_module() { free_page(new_page) ; }