ok6410 android driver(8)
In the past, we know how to create and run a simple character device driver on pc, goldfish and ok6410.
These two essays I will talk about a led device real exists on ok6410.
In this essay, we will compile a led device driver and test it.
At first, I wanna write some short summary of the different between a character device and a real device.
(1) Character device :
We always control character device in bytes, just like we control a normal file. Open file with a file handle, then read, write, llseek and put others control to the handle.
(2) Real device :
Acturely, we can control a real device like a normal character driver with file streams. But we should know more about the ioctl. When a real device connects to pc, it register a address for itself automaticly. Then the pc malloc some I/O memory for it, and they will communicate via the I/O memory instead of immediately. The I/O memory will protect both pc and device from speech mismach.
1、 leds.c
#include <linux/fs.h> #include <linux/cdev.h> #include <linux/pci.h> #include <asm/uaccess.h> #include <mach/map.h> #include <mach/regs-gpio.h> #include <mach/gpio-bank-m.h> #define DEVICE_NAME "s3c6410_leds" #define DEVICE_COUNT 1 #define S3C6410_LEDS_MAJOR 0 #define S3C6410_LEDS_MINOR 234 #define PARAM_SIZE 3 static unsigned char mem[4]; static int major = S3C6410_LEDS_MAJOR; static int minor = S3C6410_LEDS_MINOR; static dev_t leds_number; static int leds_state = 1; static char *param[] = {"string1", "string2", "string3"}; static int param_size = PARAM_SIZE; static struct class *leds_class = NULL; static long s3c6410_leds_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { switch (cmd) { unsigned int tmp; case 0: case 1: if (arg > 4) { return -EINVAL; } tmp = ioread32(S3C64XX_GPMDAT); if (cmd == 1) { tmp &= (~(1 << arg)); } else { tmp |= (1 << arg); } iowrite32(tmp, S3C64XX_GPMDAT); return 0; default : return -EINVAL; } } static ssize_t s3c6410_leds_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { unsigned tmp = count; unsigned long i = 0; memset(mem, 0, 4); if (count > 4) { tmp = 4; } if (copy_from_user(mem, buf, tmp)) { return -EINVAL; } else { for (i = 0; i < 4; i++) { tmp = ioread32(S3C64XX_GPMDAT); if (mem[i] == '1') { tmp &= (~(1 << i)); } else { tmp |= (1 << i); } iowrite32(tmp, S3C64XX_GPMDAT); } return count; } } static struct file_operations leds_fops = { .owner = THIS_MODULE, .unlocked_ioctl = s3c6410_leds_ioctl, .write = s3c6410_leds_write, }; static struct cdev leds_cdev; static int leds_create_device(void) { int ret = 0; int err = 0; cdev_init(&leds_cdev, &leds_fops); leds_cdev.owner = THIS_MODULE; if (major > 0) { leds_number = MKDEV(major, minor); err = register_chrdev_region(leds_number, DEVICE_COUNT, DEVICE_NAME); if (err < 0) { printk(KERN_WARNING "register_chardev_region() failed.\n"); return err; } } else { err = alloc_chrdev_region(&leds_cdev.dev, 10, DEVICE_COUNT, DEVICE_NAME); if (err < 0) { printk(KERN_WARNING "register_chardev_region failed.\n"); return err; } major = MAJOR(leds_cdev.dev); minor = MINOR(leds_cdev.dev); leds_number = leds_cdev.dev; } ret = cdev_add(&leds_cdev, leds_number, DEVICE_COUNT); leds_class = class_create(THIS_MODULE, DEVICE_NAME) ; device_create(leds_class, NULL, leds_number, NULL, DEVICE_NAME); return ret; } static void leds_init_gpm(int leds_default) { int tmp = 0; tmp = ioread32(S3C64XX_GPMCON); tmp &= (~0xFFFF); tmp |= 0x1111; iowrite32(tmp, S3C64XX_GPMCON); tmp = ioread32(S3C64XX_GPMPUD); tmp &= (~0xFF); tmp |= 0xAA; iowrite32(tmp, S3C64XX_GPMPUD); tmp = ioread32(S3C64XX_GPMDAT); tmp &= (~0xF); tmp |= leds_default; iowrite32(tmp, S3C64XX_GPMDAT); } static int leds_init(void) { int ret; ret = leds_create_device(); leds_init_gpm(~leds_state); printk(DEVICE_NAME "\tinitialized.\n"); printk("param0\t%s\n", param[0]); printk("param1\t%s\n", param[1]); printk("param2\t%s\n", param[2]); return ret; } static void leds_destroy_device(void) { device_destroy(leds_class, leds_number); if (leds_class) { unregister_chrdev_region(leds_number, DEVICE_COUNT); return; } } static void leds_exit(void) { leds_destroy_device(); printk(DEVICE_NAME"\texit.\n"); } module_init(leds_init); module_exit(leds_exit); module_param(leds_state, int, S_IRUGO | S_IWUSR); module_param_array(param, charp, ¶m_size, S_IRUGO | S_IWUSR); MODULE_LICENSE("GPL");
Then follow the steps we analysis in wordcount device (character device).
2、init and exit entrance functions
(1) init function :
// init entrance function module_init(leds_init); // static int leds_init(void) { ... // create device like normal character device ret = leds_create_device(); // init the device by its I/O memory leds_init_gpm(~leds_state); ... }
(2) exit function :
// exit entrance function module_exit(leds_exit); // static void leds_exit(void) { // destroy and unregister the device leds_destroy_device(); printk(DEVICE_NAME"\texit.\n"); }
3、the device mechanism supported by the device driver
// file control mechanism static struct file_operations leds_fops = { .owner = THIS_MODULE, .unlocked_ioctl = s3c6410_leds_ioctl, .write = s3c6410_leds_write, }; // device defination static struct cdev leds_cdev;
4、the callback operation functions
static struct file_operations leds_fops = { .owner = THIS_MODULE, .unlocked_ioctl = s3c6410_leds_ioctl, .write = s3c6410_leds_write, }; // static long s3c6410_leds_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) ... // static ssize_t s3c6410_leds_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) ...
I don't want to talk much about file_operations, you can check it in /kernel_dir/include/linux/fs.h and 《LDD》.
TIPS-1 :
If you want to test the device, you could use echo. But don't try to use a shell scripts to control it. because the shell in android and pc is working not like the original shell. Ok, if you turely want to got a test in shell scripts, try this in you pc : // choose no $ sudo dpkg-reconfigure dash
TIPS-2 :
Our device is /dev/s3c6410_leds instead of /dev/leds