欢迎转载,务必注明出处:http://blog.csdn.net/wang_shuai_ww/article/details/44303069
本文章是记录Android开发中驱动层、HAL层、应用层之间的关系,以及其开发方法,本文将会以实现LED的控制为例来进行记录。
一是可以给以后自己做开发做参考,二是希望可以帮助正在学习的朋友参考。
一般的app不需要我们去关注hal和驱动,但在设计一个硬件系统时,原生的Android并未提供合适的服务,所以我们才需要去了解这个流程。由于也是刚入门,很多还不太懂,朋友们有什么疑问可以留言。
首先需要了解,Android的app想要操作硬件,是什么样的一个流程。一般是这样的,app应用层、服务层、硬件抽象层、底层驱动。
我是从底层到上层来进行学习和测试的。也就是:底层->硬件抽象层->服务层->app。原因是,首先需要确定底层的驱动没有问题,而且底层驱动可以使用Linux的方法来进行测试,一步一步走到上层应用。
驱动代码我就直接贴上来,就不去详细解释里面的含义了,不懂的可以参考罗升阳的《Android系统源码情景分析》的第二章。
代码如下:
#include <linux/kernel.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/moduleparam.h> #include <linux/slab.h> #include <linux/ioctl.h> #include <linux/cdev.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/gpio.h> #include <mach/platform.h> #include <mach/devices.h> #include <mach/soc.h> #include <mach/gpio.h> #include <linux/uaccess.h> #include <linux/pci.h> #include <linux/proc_fs.h> #define DEVICE_NAME "real_led" #define LED_DEVICE_NODE_NAME DEVICE_NAME #define LED_DEVICE_FILE_NAME DEVICE_NAME #define LED_DEVICE_PROC_NAME DEVICE_NAME #define LED_DEVICE_CLASS_NAME DEVICE_NAME static int led_gpios[] = { (PAD_GPIO_C + 1), }; #define LED_NUM ARRAY_SIZE(led_gpios) /*主设备和从设备号变量*/ static int led_major = 0; static int led_minor = 0; static struct class* led_class = NULL; /*访问设置属性方法*/ static ssize_t led_val_show(struct device* dev, struct device_attribute* attr, char* buf); static ssize_t led_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count); /*定义设备属性*/ static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, led_val_show, led_val_store); struct leds_dev { struct cdev dev; int led_status; }; struct leds_dev *led_dev=NULL; /*打开设备方法*/ static int led_open(struct inode* inode, struct file* filp) { struct leds_dev* dev; /*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/ dev = container_of(inode->i_cdev, struct leds_dev, dev); filp->private_data = dev; return 0; } /*设备文件释放时调用,空实现*/ static int led_release(struct inode* inode, struct file* filp) { return 0; } /*读取设备的寄存器val的值*/ static ssize_t led_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) { ssize_t err = 0; struct leds_dev* dev = filp->private_data; if(count < sizeof(dev->led_status)) { goto out; } /*将寄存器val的值拷贝到用户提供的缓冲区*/ if(copy_to_user(buf, &(dev->led_status), sizeof(dev->led_status))) { err = -EFAULT; goto out; } err = sizeof(dev->led_status); out: return err; } /*写设备的寄存器值val*/ static ssize_t led_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) { struct leds_dev* dev = filp->private_data; ssize_t err = 0; int i; if(count != sizeof(dev->led_status)) { goto out; } /*将用户提供的缓冲区的值写到设备寄存器去*/ if(copy_from_user(&(dev->led_status), buf, count)) { err = -EFAULT; goto out; } for(i=0;i<LED_NUM;i++){ if((0x01&(dev->led_status>>i))==1) nxp_soc_gpio_set_out_value(led_gpios[i], 0); else nxp_soc_gpio_set_out_value(led_gpios[i], 1); } err = sizeof(dev->led_status); out: return err; } static int led_ioctl(struct file *file, unsigned int cmd, unsigned long num) { //由于开发板只有一个LED,所以这里做个判断,传入的num不等于0,返回错误 if(num != 0) { printk("RealARM S5P4418 board only have one led lights.Please check app input parameters.\n"); return -EINVAL; } nxp_soc_gpio_set_out_value(led_gpios[num], cmd); } /**************************************************************************************/ /*设备文件操作方法表*/ static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .read = led_read, .write = led_write, .unlocked_ioctl= led_ioctl, }; /**************************************************************************************/ static ssize_t __led_get_val(struct leds_dev* dev, char* buf) { int val = 0; val = dev->led_status; return snprintf(buf, PAGE_SIZE, "%d\n", val); } /*把缓冲区buf的值写到设备寄存器val中去,内部使用*/ static ssize_t __led_set_val(struct leds_dev* dev, const char* buf, size_t count) { int val = 0; int i; /*将字符串转换成数字*/ val = simple_strtol(buf, NULL, 10); for(i=0;i<LED_NUM;i++){ nxp_soc_gpio_set_out_value(led_gpios[i], val); } dev->led_status = val; return count; } /*读取设备属性val*/ static ssize_t led_val_show(struct device* dev, struct device_attribute* attr, char* buf) { struct leds_dev* hdev = (struct leds_dev*)dev_get_drvdata(dev); return __led_get_val(hdev, buf); } /*写设备属性val*/ static ssize_t led_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count) { struct leds_dev* hdev = (struct leds_dev*)dev_get_drvdata(dev); return __led_set_val(hdev, buf, count); } /**************************************************************************************/ /*读取设备寄存器val的值,保存在page缓冲区中*/ static ssize_t led_proc_read(char* page, char** start, off_t off, int count, int* eof, void* data) { if(off > 0) { *eof = 1; return 0; } return __led_get_val(led_dev, page); } /*把缓冲区的值buff保存到设备寄存器val中去*/ static ssize_t led_proc_write(struct file* filp, const char __user *buff, unsigned long len, void* data) { int err = 0; char* page = NULL; if(len > PAGE_SIZE) { printk(KERN_ALERT"The buff is too large: %lu./n", len); return -EFAULT; } page = (char*)__get_free_page(GFP_KERNEL); if(!page) { printk(KERN_ALERT"Failed to alloc page./n"); return -ENOMEM; } /*先把用户提供的缓冲区值拷贝到内核缓冲区中去*/ if(copy_from_user(page, buff, len)) { printk(KERN_ALERT"Failed to copy buff from user./n"); err = -EFAULT; goto out; } err = __led_set_val(led_dev, page, len); out: free_page((unsigned long)page); return err; } /*创建/proc/led文件*/ static void led_create_proc(void) { struct proc_dir_entry* entry; entry = create_proc_entry(DEVICE_NAME, 0, NULL); if(entry) { // entry->owner = THIS_MODULE; entry->read_proc = led_proc_read; entry->write_proc = led_proc_write; } } /*删除/proc/led文件*/ static void led_remove_proc(void) { remove_proc_entry(DEVICE_NAME, NULL); } /**************************************************************************************/ static int __led_setup_dev(struct leds_dev* dev) { int err; dev_t devno = MKDEV(led_major, led_minor); memset(dev, 0, sizeof(struct leds_dev)); cdev_init(&(led_dev->dev), &led_fops); dev->dev.owner = THIS_MODULE; dev->dev.ops = &led_fops; /*注册字符设备*/ err = cdev_add(&(dev->dev),devno, 1); if(err) { return err; } return 0; } static int __init real4418_led_dev_init(void) { int ret; int i; unsigned int val; int err = -1; dev_t dev = 0; struct device* temp = NULL; for (i = 0; i < LED_NUM; i++) { ret = gpio_request(led_gpios[i], "LED"); if (ret) { printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME, led_gpios[i], ret); goto fail; } nxp_soc_gpio_set_io_func(led_gpios[i], 1); nxp_soc_gpio_set_io_dir(led_gpios[i], 1); nxp_soc_gpio_set_out_value(led_gpios[i], 0); } /*动态分配主设备和从设备号*/ err = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME); if(err < 0) { printk(KERN_ALERT"Failed to alloc char dev region./n"); goto fail; } led_major = MAJOR(dev); led_minor = MINOR(dev); /*分配led设备结构体变量*/ led_dev = kmalloc(sizeof(struct leds_dev), GFP_KERNEL); if(!led_dev) { err = -ENOMEM; printk(KERN_ALERT"Failed to alloc led_dev./n"); goto unregister; } /*初始化设备*/ err = __led_setup_dev(led_dev); if(err) { printk(KERN_ALERT"Failed to setup dev: %d./n", err); goto cleanup; } /*在/sys/class/目录下创建设备类别目录led*/ led_class = class_create(THIS_MODULE, LED_DEVICE_CLASS_NAME); if(IS_ERR(led_class)) { err = PTR_ERR(led_class); printk(KERN_ALERT"Failed to create led class./n"); goto destroy_cdev; } /*在/dev/目录和/sys/class/led目录下分别创建设备文件led*/ temp = device_create(led_class, NULL, dev, "%s", LED_DEVICE_FILE_NAME); if(IS_ERR(temp)) { err = PTR_ERR(temp); printk(KERN_ALERT"Failed to create led device."); goto destroy_class; } /*在/sys/class/led/led目录下创建属性文件val*/ err = device_create_file(temp, &dev_attr_val); if(err < 0) { printk(KERN_ALERT"Failed to create attribute val."); goto destroy_device; } dev_set_drvdata(temp, led_dev); led_create_proc(); printk(DEVICE_NAME"\tinitialized\n"); return ret; destroy_device: device_destroy(led_class, dev); destroy_class: class_destroy(led_class); destroy_cdev: cdev_del(&(led_dev->dev)); cleanup: kfree(led_dev); unregister: unregister_chrdev_region(MKDEV(led_major, led_minor), 1); fail: for (; i >=0; i--) gpio_free(led_gpios[i]); return err; } static void __exit real4418_led_dev_exit(void) { int i; dev_t devno = MKDEV(led_major, led_minor); for (i = 0; i < LED_NUM; i++) { gpio_free(led_gpios[i]); } /*删除/proc/led文件*/ led_remove_proc(); /*销毁设备类别和设备*/ if(led_class) { device_destroy(led_class, MKDEV(led_major, led_minor)); class_destroy(led_class); } /*删除字符设备和释放设备内存*/ if(led_dev) { cdev_del(&(led_dev->dev)); kfree(led_dev); } /*释放设备号*/ unregister_chrdev_region(devno, 1); printk(KERN_ALERT"Destroy led device./n"); } module_init(real4418_led_dev_init); module_exit(real4418_led_dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("sean <[email protected]>");
kconfig添加配置选项:
config LEDS_REALARM tristate "LED Support for RealARM S5P4418" help This option enables support for on-chip LED drivers found on RealARM S5P4418.
obj-$(CONFIG_LEDS_REALARM) += real4418_leds.o使用make ARCH=arm xconfig命令进行内核配置。如下图所示:
编译内核,下载到开发板启动。
下面是测试上面代码的方法:
需要测试三个部分,传统设备文件接口系统、devfs文件系统接口、proc文件系统接口,这三种接口均可实现对LED的硬件操作,Android部分的hal层使用的是read和write方式。
传统的接口系统需要写Linux应用程序来进行测试,我这里就不再详细写了,可以参考《Android系统源码情景分析》里面的写法,就是使用open、read、write、unlocked_ioctl这些函数。下面是使用ioctl来进行测试的,read、write按照read、write的用法就行了。
/* ============================================== Name : led_test.c Author : sean Date : 16/3/2015 Description : s5p4418 led driver test ============================================== */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include<sys/ioctl.h> int main(int argc, char**argv) { int turn, index, fd; if(strcmp(argv[1], "on") == 0) { turn = 1; } else if(strcmp(argv[1], "off") ==0) { turn = 0; } else { printf("Usage: led_test on|off1|2|3|4\n"); exit(1); } //打开LED设备 fd = open("/dev/real_led", 0); if(fd < 0) { printf("Open Led DeviceFaild!\n"); exit(1); } printf("Open Led Device success!\n"); //IO控制 ioctl(fd, turn, 1); //关闭LED设备 close(fd); return 0; }
启动板子,进入到控制台,进入到/sys/class/real_led/real_led/目录,如果可以进入,那么说明我们led的devfs文件系统注册成功了,反之。
使用下面所示的方法进行测试:
root@realarm:/sys/class/real_led/real_led # cat val 0 root@realarm:/sys/class/real_led/real_led # echo '1' > val root@realarm:/sys/class/real_led/real_led # cat val 1 root@realarm:/sys/class/real_led/real_led # echo '0' > val root@realarm:/sys/class/real_led/real_led # cat val 0 root@realarm:/sys/class/real_led/real_led #当向val写入1时,观察LED是否被点亮了,反之。我这里测试是没有问题的。
proc文件系统的测试:
进入到/proc目录,首先使用ls real_led检查是否有real_led这个文件,然后使用echo '1' > real_led和echo '0' > real_led来测试led。
如下:
root@realarm:/proc # ls real_led real_led root@realarm:/proc # echo '1' > real_led root@realarm:/proc # echo '0' > real_led root@realarm:/proc #我这里测试也是没有问题。
这三种方式测试都没问题了,那么驱动程序是没有什么问题了,下一部分是硬件抽象层的说明。