• 本课程提供开发者学习Android底层的HAL(硬件抽象层)的开发方法和技术。HAL所在的位置是介于Android系统服务与Linux内核之间,HAL Driver是以library形式出现,给HAL Stub调用,供Android System架构者调用。而HAL ...
  • RK3288 android驱动入门

    2019-08-26 10:20:58
    本次课程包含 前面 RK3399 的内容,使用 RK3288 和 RK3399 多块开发板,带领大家把开发板上面的硬件模块功能都实现了,两个课程,大家看需求选其一即可,个人认为,RK平台功能快速实现有这些课程即可!
  • 也许是中国第一个讲解android驱动的课程,涵盖: bootloader,内核移植,INIT进程,框架(BINDER IPC,SERVICE FRAMEWORK Activity Manager Serive,JNI,HAL等),binder驱动,logger,Ashmen,电源管理,常用驱动(如灯光...
  • 于此,将框架(Framework)和设计模式(Design Pattern)应用于Linux驱动开发,...其直接的益处就是:让我们能基于一致的设计理念来结合Android HAL与Linux两层的驱动开发,规划出整体和谐Android-based软硬整合产品架构。
  • 1. 了解物联网技术的知识结构;2. 了解现代物联网嵌入式开发的发展状况,体会高效的嵌入式开发;3. 了解用最小资源完成供应链工程的方法,了解供应链关键控制节点。
  • 很久前就想了解驱动程序的想法,这里现做一个简单的开始,从demo做起,看到Android驱动程序的基本运行流程,这对漏洞分析、检测和挖掘都是必要的。同样,本篇基本也是自己学习过程的记录,无干货。本篇大多数内容...

    很久前就想了解驱动程序的想法,这里现做一个简单的开始,从demo做起,看到Android驱动程序的基本运行流程,这对漏洞分析、检测和挖掘都是必要的。同样,本篇基本也是自己学习过程的记录,无干货。本篇大多数内容来自Linux设备驱动之Ioctl控制

    一、用户层

    不管是漏洞检测,还是poc中,我们见到最多的函数就是ioctl()函数,这个函数就是用户层调用内核程序的接口。

    /*
    fd:文件描述符
    cmd:控制命令
    ...:可选参数:插入*argp,具体内容依赖于cmd
    */
    int ioctl(int fd,unsigned long cmd,...);

    函数第一个参数文件句柄,可以通过open()获得,第二个参数是指令的值,和驱动程序里的switch()里的case值是对应的,第三个是可选参数,通常是一个指针,指向某个变量或者结构体。函数执行成功,返回0,出错返回-1。

    下面来看一个用户层demo程序(代码取自Linux设备驱动之Ioctl控制,如有冒犯请联系删除),和后面驱动程序是对应的,首先定义头文件。

    #ifndef _MEMDEV_H_
    #define _MEMDEV_H_
    
    #include <linux/ioctl.h>
    
    #ifndef MEMDEV_MAJOR
    #define MEMDEV_MAJOR 0   /*预设的mem的主设备号*/
    #endif
    
    #ifndef MEMDEV_NR_DEVS
    #define MEMDEV_NR_DEVS 2    /*设备数*/
    #endif
    /*mem设备描述结构体*/
    struct mem_dev                                     
    {                                                        
      char *data;                      
      unsigned long size;       
    };
    
    /* 定义幻数 */
    #define MEMDEV_IOC_MAGIC  'k'
    
    /* 定义命令 */
    #define MEMDEV_IOCPRINT   _IO(MEMDEV_IOC_MAGIC, 1)
    #define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)
    #define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)
    
    #define MEMDEV_IOC_MAXNR 3
    
    #endif /* _MEMDEV_H_ */
    
    #ifndef MEMDEV_SIZE
    #define MEMDEV_SIZE 4096
    #endif

    用户层代码:

    #include <stdio.h>
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<fcntl.h>
    
    #include "memdev.h"  /* 包含命令定义 */
    
    int main()
    {
        int fd = 0;
        int cmd;
        int arg = 0;
        char Buf[4096];
    
    
        /*打开设备文件*/
        fd = open("/dev/memdev0",O_RDWR);
        if (fd < 0)
        {
            printf("Open Dev Mem0 Error!\n");
            return -1;
        }
    
        /* 调用命令MEMDEV_IOCPRINT */
        printf("<--- Call MEMDEV_IOCPRINT --->\n");
        cmd = MEMDEV_IOCPRINT;
        if (ioctl(fd, cmd, &arg) < 0)
            {
                printf("Call cmd MEMDEV_IOCPRINT fail\n");
                return -1;
        }
    
    
        /* 调用命令MEMDEV_IOCSETDATA */
        printf("<--- Call MEMDEV_IOCSETDATA --->\n");
        cmd = MEMDEV_IOCSETDATA;
        arg = 2007;
        if (ioctl(fd, cmd, &arg) < 0)
            {
                printf("Call cmd MEMDEV_IOCSETDATA fail\n");
                return -1;
        }
    
    
        /* 调用命令MEMDEV_IOCGETDATA */
        printf("<--- Call MEMDEV_IOCGETDATA --->\n");
        cmd = MEMDEV_IOCGETDATA;
        if (ioctl(fd, cmd, &arg) < 0)
            {
                printf("Call cmd MEMDEV_IOCGETDATA fail\n");
                return -1;
        }
        printf("<--- In User Space MEMDEV_IOCGETDATA Get Data is %d --->\n\n",arg);    
    
        close(fd);
        return 0;    
    }

    可以看到,程序调用了3次ioctl()函数,分别传递了3个cmd值,所以,我们后面可以看到,在驱动程序对应的ioctl函数里应该至少有这3个值对应的case。

    据此,可以看到在用户层调用ioctl()函数十分简单,大致流程其实就是参数构造过程,fd参数构造很简单,直接获取open()对应的设备文件即可获得句柄。剩下第二个第三个参数的构造,实际上比较麻烦。首先,我学习这个不是为了开发,而是和漏洞相关的工作。那么,这里就有几个问题:

    1) 我们需要调用的驱动功能可能没有源码,那么如何构造源码参数2?
    2)即使存在源码,我们如果要做批量的fuzz,如何比较通用的构造参数2?

    参数3是个指针,可能指向一个结构体,那么参数3构造同样存在上面的两个问题,而且更加不好解决。即使是写漏洞检测代码或者漏洞poc,找到这些结构体依赖也是一件体力活。

    二、驱动层

    在用户层调用了ioctl函数之后,内核里面对应的ioctl函数会被调用,我们暂时不去关心这中间的调用链。我们把重点放在内核层的ioctl函数以及驱动程序的执行流程上。

    首先,驱动程序不想我们常见的c语言函数,不是以main()作为函数的入口的。取而代之,在驱动程序中两个特殊的函数:

    module_init(initFunc);
    module_exit(exitFunc);

    module_init定义驱动被加载时的行为,在此函数应该完成一些初始化操作,通过“insmod 模块文件名”命令安装时,次函数会被调用。module_exit定义驱动被卸载时的行为,次函数函数中完成一些“善后工作”,通过“rmmod 模块文件名”来卸载模块时,函数会被调用。所以一个最简单的helloworld类似这样:

    int text_init(void){
        printk("<0>Hello World!");
        return 0;
    }
    
    void text_cleanup(void){
       printk("<0>Goodbye World!");
    }
    
    module_init(text_init);            //注册加载时执行的函数
    module_exit(text_cleanup);     //注册卸载时执行的函数
    

    显然,这样的helloworld定义行为太少,还有很多工作没做,比如用户层如何调用此驱动。前面我们知道,我们调用驱动程序是通过设备文件的形式的,接下来先介绍一些字符设备驱动程序的知识,linux设备驱动第三篇:如何写一个简单的字符设备驱动?这篇文章内容不错。

    1. 主设备号与此设备号

    这里写图片描述

    主设备号表示具体的驱动程序,次设备号由内核使用,表示具体的设备文件。例如,虚拟控制台和串口终端有驱动程序4管理,而不同的终端分别有不同的次设备号。

    1.1 设备号数据结构

    内核中,dev_t用于描述设备标号,为32位的int类型,前12位表示主设备号,后20位表示此设备号。dev_t和主设备号、次设备号可以通过linux中一些宏方面的转换。

    //获取主设备号
    MAJOR(dev_t dev);
    //获取次设备号
    MINOR(dev_t dev);
    //转换为dev_t
    MKDEV(int major, int minor);

    1.2 分配和释放设备号

    int register_chrdev_region(dev_t first, unsigned int count, const char name);

    first是要分配的设备编号范围的起始值。count是连续设备的编号的个数。name是和该设备编号范围关联的设备名称,他将出现在/proc/devices和sysfs中。此函数成功返回0,失败返回负的错误码。

    此函数是在已知主设备号的情况下使用,在未知主设备号的情况下,我们使用下面的函数:

    int alloc_chrdev_region(dev_t dev, unsigned int firstminor, unsigned int count, const char *name);

    dev用于输出申请到的设备编号,firstminor要使用的第一个次设备编号。

    在不使用时需要释放这些设备编号,已提供其他设备程序使用:

    void unregister_chrdev_region(dev_t dev, unsigned int count);

    此函数多在模块的清除函数中调用。

    以上完成设备编号注册。

    2. 重要数据结构

    2.1 文件操作file_operations

    file_operations数据结构定义在 <linux/fs.h>, 是一个函数指针的集合,设备所能提供的功能大部分都由此结构提供。这个数据结构里有大量指针,对应到文件的操作,部分实现即可。

    static const struct file_operations mem_fops =
    {
      .owner = THIS_MODULE,
      .open = mem_open,
      .release = mem_release,
      .ioctl = memdev_ioctl,
    };

    2.2 文件结构file

    file数据接口定义于<linux/fs.h>,其中几个重要的结构体成员如下:

    struct file_operations *f_op:就是上面刚刚介绍的文件操作的集合结构。

    mode_t f_mode:文件模式确定文件是可读的或者是可写的(或者都是), 通过位 FMODE_READ 和 FMODE_WRITE.

    loff_t f_pos:当前读写位置. loff_t 在所有平台都是 64 位。驱动可以读这个值, 如果它需要知道文件中的当前位置, 但是正常地不应该改变它。

    unsigned int f_flags:这些是文件标志, 例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC. 驱动应当检查 O_NONBLOCK 标志来看是否是请求非阻塞操作。

    void *private_data:open 系统调用设置这个指针为 NULL, 在为驱动调用 open 方法之前. 你可自由使用这个成员或者忽略它; 你可以使用这个成员来指向分配的数据, 但是接着你必须记住在内核销毁文件结构之前, 在 release 方法中释放那个内存. private_data 是一个有用的资源, 在系统调用间保留状态信息, 我们大部分例子模块都使用它。

    2.3 node结构

    inode 结构由内核在内部用来表示文件. 因此, 它和代表打开文件描述符的文件结构是不同的。可能有代表单个文件的多个打开描述符的许多文件结构, 但是它们都指向一个单个 inode 结构。

    3. 字符设备的注册

    内核中使用结构体 struct cdev来描述字符设备。

    有 2 种方法来分配和初始化一个这些结构. 如果你想在运行时获得一个独立的 cdev 结构, 你可以为此使用这样的代码:

    struct cdev *my_cdev = cdev_alloc();
    my_cdev->ops = &my_fops;

    更常使用的方法来分配和初始化:

    void cdev_init(struct cdev cdev, struct file_operations fops);

    一旦 cdev 结构建立, 最后的步骤是把它告诉内核:

    int cdev_add(struct cdev *dev, dev_t num, unsigned int count)

    这里, dev 是 cdev 结构, num 是这个设备响应的第一个设备号, count 是应当关联到设备的设备号的数目. 常常 count 是 1。

    从系统去除一个字符设备, 调用:

    void cdev_del(struct cdev *dev);

    具备了基本的知识后,再看驱动代码就简单多了:

    #include <linux/module.h>
    #include <linux/types.h>
    #include <linux/fs.h>
    #include <linux/errno.h>
    #include <linux/mm.h>
    #include <linux/sched.h>
    #include <linux/init.h>
    #include <linux/cdev.h>
    #include <asm/io.h>
    #include <asm/system.h>
    #include <asm/uaccess.h>
    
    #include "memdev.h"
    static int mem_major = MEMDEV_MAJOR;//=0
    
    module_param(mem_major, int, S_IRUGO);
    
    struct mem_dev *mem_devp; /*设备结构体指针*/
    
    struct cdev cdev; 
    
    /*struct mem_dev                                     
    {                                                        
      char *data;                      
      unsigned long size;       
    };
    */
    
    /*文件打开函数*/
    int mem_open(struct inode *inode, struct file *filp)
    {
        struct mem_dev *dev;
    
        /*获取次设备号*/
        int num = MINOR(inode->i_rdev);
    
        if (num >= MEMDEV_NR_DEVS) 
                return -ENODEV;
        dev = &mem_devp[num];
    
        /*将设备描述结构指针赋值给文件私有数据指针*/
        filp->private_data = dev;
    
        return 0; 
    }
    
    /*文件释放函数*/
    int mem_release(struct inode *inode, struct file *filp)
    {
      return 0;
    }
    
    /*IO操作*/
    int memdev_ioctl(struct inode *inode, struct file *filp,
                     unsigned int cmd, unsigned long arg)
    {
    
        int err = 0;
        int ret = 0;
        int ioarg = 0;
    
        /* 检测命令的有效性 */
        if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC) 
            return -EINVAL;
        if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR) 
            return -EINVAL;
    
        /* 根据命令类型,检测参数空间是否可以访问 */
        if (_IOC_DIR(cmd) & _IOC_READ)
            err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
        else if (_IOC_DIR(cmd) & _IOC_WRITE)
            err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
        if (err) 
            return -EFAULT;
    
        /* 根据命令,执行相应的操作 */
        switch(cmd) {
    
          /* 打印当前设备信息 */
          case MEMDEV_IOCPRINT:
              printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");
            break;
    
          /* 获取参数 */
          case MEMDEV_IOCGETDATA: 
            ioarg = 1101;
            ret = __put_user(ioarg, (int *)arg);
            break;
    
          /* 设置参数 */
          case MEMDEV_IOCSETDATA: 
            ret = __get_user(ioarg, (int *)arg);
            printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);
            break;
    
          default:  
            return -EINVAL;
        }
        return ret;
    
    }
    
    /*文件操作结构体*/
    static const struct file_operations mem_fops =
    {
      .owner = THIS_MODULE,
      .open = mem_open,
      .release = mem_release,
      .ioctl = memdev_ioctl,
    };
    
    /*设备驱动模块加载函数*/
    static int memdev_init(void)
    {
      int result;
      int i;
    
      dev_t devno = MKDEV(mem_major, 0);
    
      /* 静态申请设备号*/
      if (mem_major)
        result = register_chrdev_region(devno, 2, "memdev");
      else  /* 动态分配设备号 */
      {
        result = alloc_chrdev_region(&devno, 0, 2, "memdev");
        mem_major = MAJOR(devno);
      }  
    
      if (result < 0)
        return result;
    
      /*初始化cdev结构*/
      cdev_init(&cdev, &mem_fops);
      cdev.owner = THIS_MODULE;
      cdev.ops = &mem_fops;
    
      /* 注册字符设备 */
      cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
    
      /* 为设备描述结构分配内存*/
      mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
      if (!mem_devp)    /*申请失败*/
      {
        result =  - ENOMEM;
        goto fail_malloc;
      }
      memset(mem_devp, 0, sizeof(struct mem_dev));
    
      /*为设备分配内存*/
      for (i=0; i < MEMDEV_NR_DEVS; i++) 
      {
            mem_devp[i].size = MEMDEV_SIZE;
            mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
            memset(mem_devp[i].data, 0, MEMDEV_SIZE);
      }
    
      return 0;
    
      fail_malloc: 
      unregister_chrdev_region(devno, 1);
    
      return result;
    }
    
    /*模块卸载函数*/
    static void memdev_exit(void)
    {
      cdev_del(&cdev);   /*注销设备*/
      kfree(mem_devp);     /*释放设备结构体内存*/
      unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
    }
    
    MODULE_AUTHOR("David Xie");
    MODULE_LICENSE("GPL");
    
    module_init(memdev_init);
    module_exit(memdev_exit);
    
    展开全文
  • QQ:971586331 软件环境: 操作系统:windows 10 IDE版本:Android Studio 3.4.2 JAVA版本:jdk-8u221-windows-x64 NDK版本:android-ndk-...开发板android版本:android 4.0.3 硬件环境: 开发板:itop-44...

    QQ:971586331

    软件环境:

    操作系统:windows 10

    IDE版本:Android Studio 3.4.2

    JAVA版本:jdk-8u221-windows-x64

    NDK版本:android-ndk-r20-windows-x86_64

    Kernel版本:linux 3.0

    开发板android版本:android 4.0.3

    硬件环境:

    开发板:itop-4412 精英版

    本文内容:本文描述了如何使用android应用程序调用linux驱动控制LED灯的亮灭。要实现android应用程序控制LED,需要三个程序,LED的linux驱动,JNI库和android应用程序。android应用程序通过JNI库调用LED驱动程序,从而实现android应用程序控制LED。

    1.开发板环境搭建

    开发环境搭建请参考《iTOP-4412开发板之精英版使用手册_V3.1.pdf》,本文使用的配置是

    uboot:iTop4412_uboot_20180320.tar

    kernel:iTop4412_Kernel_3.0_20180604.tar

    android:iTop4412_ICS_git_20151120.tar

    编译完成后将ramdisk-uboot.img,system.img,zImage,u-boot-iTOP-4412.bin文件通过OTG或SD烧写到开发板的EMMC中,如果在uboot下使用OTG,发现windows 10装不上光盘中的android_drv_90000_64.exe驱动,可以谷歌搜索安装android_11000010001_x64_718.exe。

    2.LED的驱动程序

    LED驱动在kernel的drivers/char/itop4412-leds.c中,从itop4412-leds.c中我们可以得知LED驱动的设备文件名叫“leds”。驱动程序实现了ioctl函数。

    long leds_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
    {
    	printk("debug: leds_ioctl cmd is %d\n" , cmd);
    
    	switch(cmd)
    	{
    		case 0:
    		case 1:
    			if (arg > LED_NUM) {
    				return -EINVAL;
    			}
    
    			gpio_set_value(led_gpios[arg], cmd);
    			break;
    
    		default:
    			return -EINVAL;
    	}
    
    	return 0;
    }

    leds_ioctl的cmd参数表示灯的亮灯,arg参数表示灯的编号,根据文件中的定义可以知,0表示GPL2_0,也就是LED2,1表示GPK1_1,也就是LED3。

    static int led_gpios[] = {
    	EXYNOS4_GPL2(0),
    	EXYNOS4_GPK1(1),
    };

    接下来我们查看drivers/char/Makefile文件,宏CONFIG_LEDS_CTL控制LED驱动是否编译

    obj-$(CONFIG_LEDS_CTL)		+= itop4412_leds.o

    再查看drivers/char/Kconfig文件,默认就是y

    config LEDS_CTL
            bool "Enable LEDS config"
            default y
            help
              Enable LEDS config

    再查看.config,已经将LED驱动编入了内核

    CONFIG_LEDS_CTL=y

    看来板子的驱动已经做好了,完全不用我们动手,接下我们看怎么编写JNI接口调用linux驱动

    3.JNI和NDK

    因为android是使用java语言进行开发的,但linux驱动是用C语言进行开发的,所以面临java如果调用C语言接口的问题,JNI提供的API就是解决java和其他语言通信的问题。NDK 是一套工具集合,允许你使用C语言来实现应用程序的部分功能。我们写好JNI接口后使用NDK打包成库文件,就可以提供给android应用程序调用了。接下来我们新建工程编写JNI。

    新建一个空activity

    填写工程名,选择工程路径,开发语言选择java,API选择15

    创建工程后得待编译完成,然后在包名下创建一个名叫jni_led的类

    文件内容如下:

    package com.example.led_test;
    
    public class jni_led {
    
        public native static String Leds_Operation(int ledNum, boolean status); //操作接口
    }

    打开 Android Studio 的 Terminal,使用javac命令将java文件编译成.class文件

    F:\OneDrive\Linux\android_project\led_test>javac .\app\src\main\java\com\example\led_test\jni_led.java

    使用javah命令创建头文件。-encoding UTF-8表示指定编码格式,防止出现“错误: 编码GBK的不可映射字符”,-d jni表示在当前目录下创建jni目录,将生成的文件放在jni目录中,-classpath表示指定类文件的路径。这里有一个地方要注意,类文件在写的时候是包名+类名,所有路径只用写到java目录,后面的com,example和led_test虽然都是文件夹,但这里表示包名(第一次写在这里纠结了好久,我想我路径明明写对了啊,为什么找不到\app\src\main\java\com\example\led_test文件夹下的类)

    F:\OneDrive\Linux\android_project\led_test>javah -encoding UTF-8 -d jni -classpath ./app/src/main/java com.example.led_test.jni_led
    

    指令执行完成后可以发现在jni目录下生成了包名加类名的头文件

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_example_led_test_jni_led */
    
    #ifndef _Included_com_example_led_test_jni_led
    #define _Included_com_example_led_test_jni_led
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     com_example_led_test_jni_led
     * Method:    Leds_Operation
     * Signature: (IZ)Ljava/lang/String;
     */
    JNIEXPORT jstring JNICALL Java_com_example_led_1test_jni_1led_Leds_1Operation
      (JNIEnv *, jclass, jint, jboolean);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    可以发现,头文件中根据jni_led.java中定义的java类接口生成了JNI接口函数,我们要实现这个接口函数。

    然后在JNI下创建com_example_led_test_jni_led.c文件

    在com_example_led_test_jni_led.c中,我们将头文件中的接口函数据复制过来,然后使用linux API操作linux设备文件

    //
    // Created by shiyu on 2019/8/17.
    //
    
    #include<jni.h>
    #include<stdio.h>
    #include <fcntl.h>
    #include <linux/ioctl.h>
    //导入我们创建的头文件
    #include "com_example_led_test_jni_led.h"
    
    #define DEVICE_NAME		"/dev/leds"
    
    JNIEXPORT jstring JNICALL Java_com_example_led_JNITest_Leds_1Operation
      (JNIEnv *env, jclass obj, jint ledsNum, jboolean status){
    
    int leds_fd = 0;
    
    	leds_fd = open(DEVICE_NAME, O_RDWR);  //打开设备节点
    	if (leds_fd == -1) {
    		return 1;
    	}
    
    	switch (ledsNum) {
    	case 0:
    		if (status)
    			ioctl(leds_fd, 0, 0);
    		else
    			ioctl(leds_fd, 1, 0);
    		break;
    	case 1:
    		if (status)
    			ioctl(leds_fd, 0, 1);
    		else
    			ioctl(leds_fd, 1, 1);
    		break;
    	defautl :
    		break;
    	}
    
    	close(leds_fd);
    
    	return 0;  //操作成功返回0
    }

    在jni下创建一个Android.mk文件

    
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    
    LOCAL_MODULE := jni_led
    LOCAL_SRC_FILES := com_example_led_test_jni_led.c
    include $(BUILD_SHARED_LIBRARY)

    这时指定了生成库的名字和源文件,再新建一个Application.mk文件

    APP_ABI := all

    安装NDK工具集后,进入jni目录使用ndk-build命令将JNI接口程序编译成库文件

    在libs目录下生成了各种平台的库文件

    为了让项目能够找到我们的生成的库,在 build.gradle 文件夹的 android 下添加:

    sourceSets {
            main() {
                jniLibs.srcDirs = ['../libs']
                jni.srcDirs = [] //屏蔽掉默认的jni编译生成过程
            }
        }

    然后在jni_led.java中加载生成的库文件

    package com.example.led_test;
    
    public class jni_led {
        static {
            System.loadLibrary("jni_led");  //加载生成的.so文件
        }
        public native static String Leds_Operation(int ledNum, boolean status); //操作接口
    }

    接下来我们编写android应用程序利用Leds_Operation接口控制LED灯

    4.编写android应用程序

    打开工程目录下的activity_main.xml文件,添加4个button,并指写button的onClick回调函数

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <TableLayout
            android:id="@+id/TableLayout2"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:collapseColumns="4" >
    
            <TableRow>
    
                <Button
                    android:id="@+id/button_led3off"
                    android:layout_width="200dp"
                    android:layout_height="200dp"
                    android:onClick="led3_off_click"
                    android:text="led3_off"
                    tools:layout_editor_absoluteX="228dp"
                    tools:layout_editor_absoluteY="186dp" />
    
                <Button
                    android:id="@+id/button_led3on"
                    android:layout_width="200dp"
                    android:layout_height="200dp"
                    android:onClick="led3_on_click"
                    android:text="led3_on"
                    tools:layout_editor_absoluteX="98dp"
                    tools:layout_editor_absoluteY="186dp" />
    
                <Button
                    android:id="@+id/button_led2off"
                    android:layout_width="200dp"
                    android:layout_height="200dp"
                    android:onClick="led2_on_click"
                    android:text="led2_off"
                    tools:layout_editor_absoluteX="228dp"
                    tools:layout_editor_absoluteY="100dp" />
    
                <Button
                    android:id="@+id/button_led2on"
                    android:layout_width="200dp"
                    android:layout_height="200dp"
                    android:onClick="led2_off_click"
                    android:text="led2_on"
                    tools:layout_editor_absoluteX="98dp"
                    tools:layout_editor_absoluteY="100dp" />
            </TableRow>
        </TableLayout>
    
    </androidx.constraintlayout.widget.ConstraintLayout>

    我们为4个按键指定了4个回调函数据,接下来我们在MainActivity.java中实现这4个回调函数

    package com.example.led_test;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.os.Bundle;
    import android.view.View;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        public void led2_on_click( View view )
        {
            jni_led.Leds_Operation(0, false);
        }
    
        public void led2_off_click( View view )
        {
            jni_led.Leds_Operation(0, true);
        }
    
        public void led3_on_click( View view )
        {
            jni_led.Leds_Operation(1, false);
        }
    
        public void led3_off_click( View view )
        {
            jni_led.Leds_Operation(1, true);
        }
    }
    

    如上,我们实现了这4个回调函数,调用jni_led库中的Leds_Operation函数,Leds_Operation会调用Java_com_example_led_JNITest_Leds_1Operation函数,这样就实现了android应用程序调用linux驱动接口。

    连接开发板,编译运行。

     

     

    展开全文
  • sky我所做的事情都是源于自己对梦想的追求--分享技术、共同创造新世界---欢迎交流:zhangbinghua2012@163.com博客园首页新随笔联系订阅管理Android 开发之 ---- 底层驱动开发(一) 【转】转自:...驱动概述 ...

    sky

    我所做的事情都是源于自己对梦想的追求--分享技术、共同创造新世界---欢迎交流:zhangbinghua2012@163.com

    Android 开发之 ---- 底层驱动开发(一) 【转】

    转自:http://blog.csdn.net/jmq_0000/article/details/7372783

    驱动概述

            说到 Android 驱动是离不开 Linux 驱动的。Android 内核采用的是 Linux2.6 内核 (最近Linux 3.3 已经包含了一些 Android 代码)。但 Android 并没有完全照搬 Linux 系统内核,除了对Linux 进行部分修正,还增加了不少内容。android 驱动 主要分两种类型:Android 专用驱动 和 Android 使用的设备驱动(linux)。

          Android 专有驱动程序:

          1)Android Ashmem 匿名共享内存; 为用户空间程序提供分配内存的机制,为进程间提供大块共享内存,同时为内核提供回收和管理这个内存。

          2)Android Logger    轻量级的LOG(日志) 驱动;

          3)Android Binder     基于 OpenBinder 框架的一个驱动;

          4)Android Power Management  电源管理模块;

          5)Low Memory Killer  低内存管理器;

          6)Android PMEM        物理内存驱动;

          7)USB Gadget             USB 驱动(基于 gaeget 框架);

          8)Ram Console           用于调试写入日志信息的设备;

          9)Time Device             定时控制设备;  

         10)Android Alarm         硬件时钟;


         Android 上的设备驱动:

          1)Framebuff 显示驱动;

          2)Event 输入设备驱动;

          3)ALSA 音频驱动;

          4)OSS 音频驱动;

          5)v412摄像头:视频驱动;

          6)MTD 驱动;

          7)蓝牙驱动;

          8)WLAN 设备驱动;


     Android 专有驱动程序

          1.Android Ashmem 

                   为用户空间程序提供分配内存的机制,为进程间提供大块共享内存,同时为内核提供回收和管理这个内存。

                   设备节点:/dev/ashmen .主设备号 10.

                   源码位置: include/linux/ashmen.h    Kernel /mm/ashmen.c

                         相比于 malloc 和 anonymous/named mmap 等传统的内存分配机制,其优势是通过内核驱动提供了辅助内核的内存回收算法机制(pin/unoin)

          2.Android Logger  

                        无论是底层的源代码还上层的应用,我们都可以使用 logger 这个日志设备看、来进行调试。

                         设备节点:  /dev/log/main      /dev/log/event   /dev/log/radio

                         源码位置:include/linux/logger.h         include/linux/logger.c

          3.Android Binder     

                    IPC Binder 一种进程间通信机制。他的进程能够为其它进程提供服务 ----- 通过标准的 Linux 系统调用 API。

                    设备节点 :/dev/binder

                    源码位置:Kernel/include/linux/binder.h    Kernel/drivers/misc/binder.c

          4.Android Power Management  

                   一个基于标准 linux 电源管理的轻量级 Android 电源管理系统,在 drivers/android/power.c      kernel/power/

          5.Low Memory Killer 

                    它在用户空间中指定了一组内存临界值,当其中某个值与进程描述中的 oom_adj 值在同一范围时,该进程将被Kill掉(在parameters/adj中指定oome_adj 的最小值)。它与标准的Linux OOM机制类似,只是实现方法不同

                    源码位置:drivers/misc/lowmemorykiller.c       

          6.Android PMEM       

                    PMEM 主要作用就是向用户空间提供连续的物理内存区域。

                          1.让 GPU 或 VPU 缓冲区共享 CPU 核心。

                          2.用于 Android service 堆。

                   源码位置:include/linux/android_pmem.h drivers/android/pmem.c                        

          7.USB Gadget            

                    基于标准 Linux USB gaeget 驱动框架的设备驱动。

                    源码位置:drivers/usb/gadet/ 

          8.Ram Console         

                    为了提供调试功能,android 允许将调试日志信息写入这个设备,它是基于 RAM 的 buffer.

                    源码位置: drivers/staging/android/ram_console.c

          9.Time Device            

                   定时控制,提供了对设备进行定时控制的功能。

                   源码位置:drivers/staging/android/timed_output.c(timed_gpio.c)

        10.Android Alarm       

                    提供一个定时器,用于把设备从睡眠状态唤醒,同时它还提供了一个即使在设备睡眠时也会运行的时钟基准。

                     设备节点:/dev/alarm

                     源码位置:drivers/trc/alarm.c


    Android 设备驱动

        1. Framebuffer 帧缓存设备

             Framebuffer 驱动在 Linux 中是标准的显示设备的驱动。对于 PC 系统,它是显卡的驱动 ; 对于嵌入式 SOC 处理器系统,它是 LCD 控制器或者其他显示控制器的驱动。它是一个字符设备,在文件系统中设备节点通常是 /dev/fbx 。 每个系统可以有多个显示设备 , 依次用 /dev/fbO 、 /dev/fb l
    等来表示。在 Android 系统中主设备号为 29 ,次设备号递增生成。

             Android 对 Framebuffer 驱动的使用方式是标准的 , 在 / dev / graphie / 中的 Framebuffer 设备节点由 init 进程自动创建 , 被 libui 库调用 。 Android 的 GUI 系统中 , 通过调用 Framebuffer 驱动的标准接口,实现显示设备的抽象。

             

         Framebuff的结构框架和实现 : 

              linux LCD驱动(二)--FrameBuffer  

                  Linux LCD驱动(四)--驱动的实现                                    

     

        2.Event输入设备驱动

             Input 驱动程序是 Linux 输入设备的驱动程序 , 分为游戏杆 (joystick) 、 鼠标 (mouse 和 mice)和事件设备 (Event queue)3 种驱动程序。其中事件驱动程序是目前通用的程序,可支持键盘 、 鼠标、触摸屏等多种输入设备。 Input 驱动程序的主设备号是 l3 ,每一种 Input 设备从设备号占 用5 位 , 3 种从设备号分配是 : 游戏杆 0 ~ 61 ; Mouse 鼠标 33 ~ 62 ; Mice 鼠标 63 ; 事件设备 64 ~ 95 ,各个具体的设备在 misc 、 touchscreen 、 keyboard 等目录中。
            Event 设备在用户空问使用 read 、 ioctl 、 poll 等文件系统的接口操作, read 用于读取输入信息, ioctl 用于获取和设置信息, poll 用于用户空间的阻塞,当内核有按键等中断时,通过在中断中唤醒内核的 poll 实现。 

            Event 输入驱动的架构和实现:

                              Linux设备驱动之——input子系统

     

         3.ALSA音频驱动

             高级 Linux 声音体系 ALSA(Advanced Linux Sound Architecture ) 是为音频系统提供驱动 的Linux 内核组件,以替代原先的开发声音系统 OSS 。它是一个完全开放源代码的音频驱动程序集 ,除了像 OSS 那样提供一组内核驱动程序模块之外 , ALSA 还专门为简化应用程序的编写提供相应的函数库,与 OSS 提供的基于 ioctl 等原始编程接口相比, ALSA 函数库使用起来要更加方便一些 

            利用该函数库,开发人员可以方便、快捷地开发出自己的应用程序,细节则留给函数库进行内部处理 。 所以虽然 ALSA 也提供了类似于 OSS 的系统接口 , 但建议应用程序开发者使用音频函数库,而不是直接调用驱动函数。

                         ALSA 驱动的主设备号为 116 ,次设备号由各个设备单独定义,主要的设备节点如下:
                                 / dev / snd / contmlCX —— 主控制 ;
                                 / dev / snd / pcmXXXc —— PCM 数据通道 ;
                                 / dev / snd / seq —— 顺序器;
                                 / dev / snd / timer —— 定义器。
            在用户空问中 , ALSA 驱动通常配合 ALsA 库使用 , 库通过 ioctl 等接口调用 ALSA 驱动程序的设备节点。对于 AIJSA 驱动的调用,调用的是用户空间的 ALsA 库的接口,而不是直接调用  ALSA 驱动程序。 ALSA 音频驱动的架构如下图所示:

                                         

            ALSA 驱动程序的主要头文件是 include / sound ./ sound . h ,驱动核心数据结构和具体驱动的注册函数是 include / sound / core . h ,驱动程序 的核心实现是 Sound / core / sound . c 文件。                     

           ALSA 驱动程序使用下面的函数注册控制和设备:

                    int snd _ pcm _ new (struct snd _ card * card , char * id , int device , int playback _ count , int capture _ count , struct snd _ pcm ** rpcm) ;

                     int snd ctl _ add(struct snd _ card * card , struct snd _ kcontrol * kcontro1) ;

             ALSA 音频驱动在内核进行 menuconfig 配置时 , 配置选项为 “ Device Drivers ” > “ Sound c ard support ” 一 > “ Advanced Linux Sound Architecture ” 。子选项包含了 Generic sound devices( 通用声音设备 ) 、 ARM 体系结构支持,以及兼容 OSS 的几个选项。 ALsA 音频驱动配置对应的文件是sound / core / Kconfig 。

          Android 没有直接使用 ALSA 驱动,可以基于 A-LSA 驱动和 ALSA 库实现 Android Audio 的硬件抽象层; ALSA 库调用内核的 ALSA 驱动, Audio 的硬件抽象层调用 ALSA 库。      


          4.OSS音频驱动

             OSS(Open Sound System开放声音系统)是 linux 上最早出现的声卡驱动。OSS 由一套完整的内核驱动程序模块组成,可以为绝大多数声卡提供统一的编程接口。

             OSS 是字符设备,主设备号14,主要包括下面几种设备文件:

              1) /dev/sndstat

                     它是声卡驱动程序提供的简单接口,它通常是一个只读文件,作用也只限于汇报声卡的当前状态。(用于检测声卡)

              2)/dev/dsp

                     用于数字采样和数字录音的设备文件。对于音频编程很重要。实现模拟信号和数字信号的转换。

              3)/dev/audio

                     类似于/dev/dsp,使用的是 mu-law 编码方式。

              4)/dev/mixer

                     用于多个信号组合或者叠加在一起,对于不同的声卡来说,其混音器的作用可能各不相同。

              5)/dev/sequencer

                       这个设备用来对声卡内建的波表合成器进行操作,或者对 MIDI 总线上的乐器进行控制。

               OSS 驱动所涉及的文件主要包括:

                    kernel/include/linux/soundcard.h

                    kernel/include/linux/sound.h   定义 OSS 驱动的次设备号和注册函数

                    kernel/sound_core.c    OSS核心实现部分

               OSS驱动架构图:

           

         5.V4l2视频驱动

       V4L2是V4L的升级版本,为linux下视频设备程序提供了一套接口规范。包括一套数据结构和底层V4L2驱动接口。V4L2提供了很多访问接口,你可以根据具体需要选择操作方法。需要注意的是,很少有驱动完全实现了所有的接口功能。所以在使用时需要参考驱动源码,或仔细阅读驱动提供者的使用说明。

          V4L2的主设备号是81,次设备号:0~255,这些次设备号里也有好几种设备(视频设备、Radio设备、Teletext、VBI)。

            V4L2的设备节点: /dev/videoX, /dev/vbiX and /dev/radioX

          V4L2框架图:

            

    【作者】张昺华
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【facebook】 张昺华 zhangbinghua
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
    posted @ 2016-11-18 15:17 张昺华-sky 阅读(3711) 评论(0) 编辑 收藏
    张昺华-sky的头像
    昵称:张昺华-sky
    园龄:3年5个月
    粉丝:107
    关注:10
    <2018年5月>
    293012345
    6789101112
    13141516171819
    20212223242526
    272829303112
    3456789
     
    Copyright ©2018 张昺华-sky




    展开全文
  • Android驱动开发全过程

    2019-07-01 21:35:10
    Android驱动开发全过程(有图有真相) 前言 意外在网上发现了这扁文章,看后感觉很有必要分享,所以整理并上传,希望大家喜欢。 Android硬件抽象层(HAL)概要介绍和学习计划 Android 的硬件抽象层,简单来说,...

    Android驱动开发全过程(有图有真相)

    前言

    意外在网上发现了这扁文章,看后感觉很有必要分享,所以整理并上传,希望大家喜欢。

    Android 硬件抽象层(HAL)概要介绍和学习计划

    Android 的硬件抽象层,简单来说,就是对Linux 内核驱动程序的封装,向上提供接口,屏蔽低层的实现细节。也就是说,把

    对硬件的支持分成了两层,一层放在用户空间(User Space),一层放在内核空间(Kernel Space),其中,硬件抽象层运行在

    用户空间,而Linux 内核驱动程序运行在内核空间。为什么要这样安排呢?把硬件抽象层和内核驱动整合在一起放在内核空间

    不可行吗?从技术实现的角度来看,是可以的,然而从商业的角度来看,把对硬件的支持逻辑都放在内核空间,可能会损害

    厂家的利益。我们知道,Linux 内核源代码版权遵循GNU License,而Android 源代码版权遵循Apache License前者在发布产

     品时,必须公布源代码而后者无须发布源代码。如果把对硬件支持的所有代码都放在Linux 驱动层,那就意味着发布时要公

    开驱动程序的源代码,而公开源代码就意味着把硬件的相关参数和实现都公开了,在手机市场竞争激烈的今天,这对厂家来

    说,损害是非常大的。因此,Android 才会想到把对硬件的支持分成硬件抽象层和内核驱动层,内核驱动层只提供简单的访问

    硬件逻辑,例如读写硬件寄存器的通道,至于从硬件中读到了什么值或者写了什么值到硬件中的逻辑,都放在硬件抽象层中

    去了,这样就可以把商业秘密隐藏起来了。也正是由于这个分层的原因,Android 被踢出了Linux 内核主线代码树中。大家想

    想,Android 放在内核空间的驱动程序对硬件的支持是不完整的,把Linux 内核移植到别的机器上去时,由于缺乏硬件抽象层

    的支持,硬件就完全不能用了,这也是为什么说Android 是开放系统而不是开源系统的原因。撇开这些争论,学习Android 硬

    件抽象层,对理解整个Android 整个系统,都是极其有用的,因为它从下到上涉及到了Android 系统的硬件驱动层、硬件抽象

    层、运行时库和应用程序框架层等等,下面这个图阐述了硬件抽象层在Android 系统中的位置,以及它和其它层的关系:

    在学习Android 硬件抽象层的过程中,我们将会学习如何在内核空间编写硬件驱动程序、如何在硬件抽象层中添加接口支持访问硬件、如何在系统启动时提供硬件访问服务以及 如何编写JNI 使得可以通过Java 接口来访问硬件,而作为中间的一个小插曲,我们还将学习一下如何在Android 系统中添加一个C可执行程序来访问硬件驱动程序。由于这是一个系统的学习过程,笔者将分成六篇文章来描述每一个学习过程,包括:

    一. 在Android 内核源代码工程中编写硬件驱动程序。

    二. 在Android 系统中增加C 可执行程序来访问硬件驱动程序。

    三. 在Android 硬件抽象层增加接口模块访问硬件驱动程序。

    四. 在Android 系统中编写JNI 方法在应用程序框架层提供Java 接口访问硬件。

    五. 在Android 系统的应用程序框架层增加硬件服务接口。

    六. 在Android 系统中编写APP 通过应用程序框架层访问硬件服务。

    学习完这六篇文章,相信大家对Android 系统就会有一个更深刻的认识了,敬请关注。

    Ubuntu 上为Android 系统编写Linux 内核驱动程序

    这里,我们不会为真实的硬件设备编写内核驱动程序。为了方便描述为Android 系统编写内核驱动程序的过程,我们使用一

    个虚拟的硬件设备,这个设备只有一个4 字节的寄存器,它可读可写。想起我们第一次学习程序语言时,都喜欢用“Hello, World”

    作为例子,这里,我们就把这个虚拟的设备命名为“hello”,而这个内核驱动程序也命名为hello 驱动程序。其实,Android 内

    核驱动程序和一般Linux 内核驱动程序的编写方法是一样的,都是以Linux 模块的形式实现的,具体可参考前面Android 学习

    启动篇一文中提到的Linux Device Drivers 一书。不过,这里我们还是从Android 系统的角度来描述Android 内核驱动程序的编

    写和编译过程。

    一. 参照这两篇文章在Ubuntu 上下载、编译和安装Android 最新源代码和在Ubuntu 上下载、编译和安装Android 最新内核源

    代码(Linux Kernel)准备好Android 内核驱动程序开发环境。

    二.  进入到kernel/common/drivers 目录,新建hello 目录

    linuxidc@www.linuxidc.com:~/Android$ cd kernel/common/drivers

    linuxidc@www.linuxidc.com:~/Android/kernel/common/drivers$ mkdir hello

     

    三. 在hello 目录中增加hello.h 文件接口部分

    1.#ifndef _HELLO_Android_H_

    2.#define _HELLO_ANDROID_H_

    3.#include <linux/cdev.h>

    4.#include <linux/semaphore.h>

    5.#define HELLO_DEVICE_NODE_NAME "hello"

    8.#define HELLO_DEVICE_FILE_NAME "hello"

    9.#define HELLO_DEVICE_PROC_NAME "hello"

    10.#define HELLO_DEVICE_CLASS_NAME "hello"

    12.struct hello_android_dev {

    13. int val;

    14. struct semaphore sem;

    15. struct cdev dev;

    16.};

    17.#endif

    这个头文件定义了一些字符串常量宏,在后面我们要用到。此外,还定义了一个字符设备结构体hello_Android_dev,这个就

    是我们虚拟的硬件设备了,val 成员变量就代表设备里面的寄存器,它的类型为int,sem 成员变量是一个信号量,是用同步

    访问寄存器val 的,dev 成员变量是一个内嵌的字符设备,这个Linux 驱动程序自定义字符设备结构体的标准方法。

    四.  在hello 目录中增加hello.c 文件,这是驱动程序的实现部分。

    驱动程序的功能主要是向上层提供访问设备的寄存器的值,包括读和写。这里,

    提供了三种访问设备寄存器的方法,

    一是通过proc 文件系统来访问,

    二是通过传统的设备文件的方法来访问,

    三是通过devfs 文件系统来访问。

     

    下面分段描述该驱动程序的实现。

    首先是包含必要的头文件和定义三种访问设备的方法

    1.#include <linux/init.h>

    2.#include <linux/module.h>

    3.#include <linux/types.h>

    4.#include <linux/fs.h>

    5.#include <linux/proc_fs.h>

    6.#include <linux/device.h>

    7.#include <asm/uaccess.h>

    8.

    9.#include "hello.h"

    10.

    11./*主设备和从设备号变量*/

    12.static int hello_major = 0;

    13.static int hello_minor = 0;

    14.

    15./*设备类别和设备变量*/

    16.static struct class* hello_class = NULL;

    17.static struct hello_Android_dev* hello_dev = NULL;

    18.

    19./*传统的设备文件操作方法*/ open/release/read/write

    20.static int hello_open(struct inode* inode, struct file* filp);

    21.static int hello_release(struct inode* inode, struct file* filp);

    22.static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);

    23.static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);

    24.

    25./*设备文件操作方法表*/

    26.static struct file_operations hello_fops = {

    27. .owner = THIS_MODULE,

    28. .open = hello_open,

    29. .release = hello_release,

    30. .read = hello_read,

    31. .write = hello_write,

    32.};

    33.

    34./*定义设备属性*/

    35.static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);

    36.

    37./*访问设置属性方法*/

    38.static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf);

    39.static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count); 定义传统的设备文

    件访问方法,主要是定义hello_open、hello_release、hello_read 和hello_write 这四个打开、释放、读和写设备文件的方法:

    1./*打开设备方法*/

    2.static int hello_open(struct inode* inode, struct file* filp) {

    3. struct hello_Android_dev* dev;

    4.

    5. /*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/

    6. dev = container_of(inode->i_cdev, struct hello_android_dev, dev);

    7. filp->private_data = dev;

    8.

    9. return 0;

    10.}

    11.

    12./*设备文件释放时调用,空实现*/

    13.static int hello_release(struct inode* inode, struct file* filp) {

    14. return 0;

    15.}

    16.

    17./*读取设备的寄存器val 的值*/

    18.static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) {

    19. ssize_t err = 0;

    20. struct hello_android_dev* dev = filp->private_data;

    21.

    22. /*同步访问*/

    23. if(down_interruptible(&(dev->sem))) {

    24. return -ERESTARTSYS;

    25. }

    26.

    27. if(count < sizeof(dev->val)) {

    28. goto out;

    29. }

    30.

    31. /*将寄存器val 的值拷贝到用户提供的缓冲区*/

    32. if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) {

    33. err = -EFAULT;

    34. goto out;

    35. }

    36.

    37. err = sizeof(dev->val);

    38.

    39.out:

    40. up(&(dev->sem));

    41. return err;

    42.}

    43.

    44./*写设备的寄存器值val*/

    45.static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) {

    46. struct hello_android_dev* dev = filp->private_data;

    47. ssize_t err = 0;

    48.

    49. /*同步访问*/

    50. if(down_interruptible(&(dev->sem))) {

    51. return -ERESTARTSYS;

    52. }

    53.

    54. if(count != sizeof(dev->val)) {

    55. goto out;

    56. }

    57.

    58. /*将用户提供的缓冲区的值写到设备寄存器去*/

    59. if(copy_from_user(&(dev->val), buf, count)) {

    60. err = -EFAULT;

    61. goto out;

    62. }

    63.

    64. err = sizeof(dev->val);

    65.

    66.out:

    67. up(&(dev->sem));

    68. return err;

    69.}

    定义通过devfs 文件系统访问方法  show/store/get/set

    这里把设备的寄存器val 看成是设备的一个属性,通过读写这个属性来对设备进行访问,

    主要是实现hello_val_show 和hello_val_store 两个方法,同时定义了两个内部使用的访问val 值的方法__hello_get_val 和

    __hello_set_val:

    1. /*读取寄存器val 的值到缓冲区buf 中,内部使用*/

    2. static ssize_t __hello_get_val(struct hello_Android_dev* dev, char* buf) {

    3. int val = 0;

    4.

    5. /*同步访问*/

    6. if(down_interruptible(&(dev->sem))) {

    7. return -ERESTARTSYS;

    8. }

    9.

    10. val = dev->val;

    11. up(&(dev->sem));

    12.

    13. return snprintf(buf, PAGE_SIZE, "%d/n", val);

    14. }

    15.

    16. /*把缓冲区buf 的值写到设备寄存器val 中去,内部使用*/

    17. static ssize_t __hello_set_val(struct hello_Android_dev* dev, const char* buf, size_t count) {

    18. int val = 0;

    19.

    20. /*将字符串转换成数字*/

    21. val = simple_strtol(buf, NULL, 10);

    22.

    23. /*同步访问*/

    24. if(down_interruptible(&(dev->sem))) {

    25. return -ERESTARTSYS;

    26. }

    27.

    28. dev->val = val;

    29. up(&(dev->sem));

    30.

    31. return count;

    32. }

    33.

    34. /*读取设备属性val*/

    35. static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf) {

    36. struct hello_Android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);

    37.

    38. return __hello_get_val(hdev, buf);

    39. }

    40.

    41. /*写设备属性val*/

    42. static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t coun

    t) {

    43. struct hello_Android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);

    44.

    45. return __hello_set_val(hdev, buf, count);

    46. }

    定义通过proc 文件系统访问方法. read/write/create/remove

    主要实现了hello_proc_read 和hello_proc_write 两个方法同时定义了在proc 文件系统

    创建和删除文件的方法hello_create_proc 和hello_remove_proc:

    1. /*读取设备寄存器val 的值,保存在page 缓冲区中*/

    2. static ssize_t hello_proc_read(char* page, char** start, off_t off, int count, int* eof, void* data) {

    3. if(off > 0) {

    4. *eof = 1;

    5. return 0;

    6. }

    7.

    8. return __hello_get_val(hello_dev, page);

    9. }

    10.

    11. /*把缓冲区的值buff 保存到设备寄存器val 中去*/

    12. static ssize_t hello_proc_write(struct file* filp, const char __user *buff, unsigned long len, void* data) {

    13. int err = 0;

    14. char* page = NULL;

    15.

    16. if(len > PAGE_SIZE) {

    17. printk(KERN_ALERT"The buff is too large: %lu./n", len);

    18. return -EFAULT;

    19. }

    20.

    21. page = (char*)__get_free_page(GFP_KERNEL);

    22. if(!page) {

    23. printk(KERN_ALERT"Failed to alloc page./n");

    24. return -ENOMEM;

    25. }

    26.

    27. /*先把用户提供的缓冲区值拷贝到内核缓冲区中去*/

    28. if(copy_from_user(page, buff, len)) {

    29. printk(KERN_ALERT"Failed to copy buff from user./n");

    30. err = -EFAULT;

    31. goto out;

    32. }

    33.

    34. err = __hello_set_val(hello_dev, page, len);

    35.

    36. out:

    37. free_page((unsigned long)page);

    38. return err;

    39. }

    40.

    41. /*创建/proc/hello 文件*/

    42. static void hello_create_proc(void) {

    43. struct proc_dir_entry* entry;

    44.

    45. entry = create_proc_entry(HELLO_DEVICE_PROC_NAME, 0, NULL);

    46. if(entry) {

    47. entry->owner = THIS_MODULE;

    48. entry->read_proc = hello_proc_read;

    49. entry->write_proc = hello_proc_write;

    50. }

    51. }

    52.

    53. /*删除/proc/hello 文件*/

    54. static void hello_remove_proc(void) {

    55. remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL);

    56. }

    最后,定义模块加载和卸载方法,这里只要是执行设备注册和初始化操作:

    1./*初始化设备*/

    2.static int __hello_setup_dev(struct hello_Android_dev* dev) {

    3. int err;

    4. dev_t devno = MKDEV(hello_major, hello_minor);

    5.

    6. memset(dev, 0, sizeof(struct hello_Android_dev));

    7.

    8. cdev_init(&(dev->dev), &hello_fops);

    9. dev->dev.owner = THIS_MODULE;

    10. dev->dev.ops = &hello_fops;

    11.

    12. /*注册字符设备*/

    13. err = cdev_add(&(dev->dev),devno, 1);

    14. if(err) {

    15. return err;

    16. }

    17.

    18. /*初始化信号量和寄存器val 的值*/

    19. init_MUTEX(&(dev->sem));

    20. dev->val = 0;

    21.

    22. return 0; 23.}

    24.

    25./*模块加载方法*/

    26.static int __init hello_init(void){

    27. int err = -1;

    28. dev_t dev = 0;

    29. struct device* temp = NULL;

    30.

    31. printk(KERN_ALERT"Initializing hello device./n");

    32.

    33. /*动态分配主设备和从设备号*/

    34. err = alloc_chrdev_region(&dev, 0, 1, HELLO_DEVICE_NODE_NAME);

    35. if(err < 0) {

    36. printk(KERN_ALERT"Failed to alloc char dev region./n");

    37. goto fail;

    38. }

    39.

    40. hello_major = MAJOR(dev);

    41. hello_minor = MINOR(dev);

    42.

    43. /*分配helo 设备结构体变量*/

    44. hello_dev = kmalloc(sizeof(struct hello_Android_dev), GFP_KERNEL);

    45. if(!hello_dev) {

    46. err = -ENOMEM;

    47. printk(KERN_ALERT"Failed to alloc hello_dev./n");

    48. goto unregister;

    49. }

    50.

    51. /*初始化设备*/

    52. err = __hello_setup_dev(hello_dev);

    53. if(err) {

    54. printk(KERN_ALERT"Failed to setup dev: %d./n", err);

    55. goto cleanup;

    56. }

    57.

    58. /*在/sys/class/目录下创建设备类别目录hello*/

    59. hello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME);

    60. if(IS_ERR(hello_class)) {

    61. err = PTR_ERR(hello_class);

    62. printk(KERN_ALERT"Failed to create hello class./n");

    63. goto destroy_cdev;

    64. }

    65.

    66. /*在/dev/目录和/sys/class/hello 目录下分别创建设备文件hello*/

    67. temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);

    68. if(IS_ERR(temp)) {

    69. err = PTR_ERR(temp);

    70. printk(KERN_ALERT"Failed to create hello device.");

    71. goto destroy_class;

    72. }

    73.

    74. /*在/sys/class/hello/hello 目录下创建属性文件val*/

    75. err = device_create_file(temp, &dev_attr_val);

    76. if(err < 0) {

    77. printk(KERN_ALERT"Failed to create attribute val.");

    78. goto destroy_device;

    79. }

    80.

    81. dev_set_drvdata(temp, hello_dev);

    82.

    83. /*创建/proc/hello 文件*/

    84. hello_create_proc();

    85.

    86. printk(KERN_ALERT"Succedded to initialize hello device./n");

    87. return 0;

    88.

    89.destroy_device:

    90. device_destroy(hello_class, dev);

    91.

    92.destroy_class:

    93. class_destroy(hello_class);

    94.

    95.destroy_cdev:

    96. cdev_del(&(hello_dev->dev));

    97.

    98.cleanup:

    99. kfree(hello_dev);

    100.

    101.unregister:

    102. unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1);

    103.

    104.fail:

    105. return err;

    106.}

    107.

    108./*模块卸载方法*/

    109.static void __exit hello_exit(void) {

    110. dev_t devno = MKDEV(hello_major, hello_minor);

    111.

    112. printk(KERN_ALERT"Destroy hello device./n");

    113.

    114. /*删除/proc/hello 文件*/

    115. hello_remove_proc();

    116.

    117. /*销毁设备类别和设备*/

    118. if(hello_class) {

    119. device_destroy(hello_class, MKDEV(hello_major, hello_minor));

    120. class_destroy(hello_class);

    121. }

    122.

    123. /*删除字符设备和释放设备内存*/

    124. if(hello_dev) {

    125. cdev_del(&(hello_dev->dev));

    126. kfree(hello_dev);

    127. }

    128.

    129. /*释放设备号*/

    130. unregister_chrdev_region(devno, 1);

    131.}

    132.

    133.MODULE_LICENSE("GPL");

    134.MODULE_DESCRIPTION("First Android Driver");

    135.

    136.module_init(hello_init);

    137.module_exit(hello_exit);

    五. 在hello 目录中新增Kconfig 和Makefile 两个文件,其中Kconfig 是在编译前执行配置命令make menuconfig 时用到的,而

    Makefile 是执行编译命令make 是用到的:

    Kconfig 文件的内容

    config HELLO

    tristate "First Android Driver"

    default n

    help

    This is the first Android driver.

    Makefile 文件的内容

    obj-$(CONFIG_HELLO) += hello.o

    在Kconfig 文件中,tristate 表示编译选项HELLO 支持在编译内核时,hello 模块支持以模块、内建和不编译三种编译方

    法,默认是不编译,因此,在编译内核前,我们还需要执行make menuconfig 命令来配置编译选项,使得hello 可以以模块或

    者内建的方法进行编译。

    在Makefile 文件中,根据选项HELLO 的值,执行不同的编译方法。

    六. 修改arch/arm/Kconfig 和drivers/kconfig 两个文件,在menu "Device Drivers"和endmenu 之间添加一行:

    source "drivers/hello/Kconfig"

    这样,执行make menuconfig 时,就可以配置hello 模块的编译选项了。.

    七. 修改drivers/Makefile 文件,添加一行:

    obj-$(CONFIG_HELLO) += hello/

    八. 配置编译选项:

    linuxidc@www.linuxdc.com:~/Android/kernel/common$ make menuconfig

    找到"Device Drivers" => "First Android Drivers"选项,设置为y。

    注意,如果内核不支持动态加载模块,这里不能选择m,虽然我们在Kconfig 文件中配置了HELLO 选项为tristate。

    要支持动态加载模块选项,必须要在配置菜单中选择Enable loadable module support 选项;在支持动态卸载模块选项,必须

    要在Enable loadable module support 菜单项中,选择Module unloading 选项。

    九. 编译:

    linuxidc@www.linuxdc.com:~/Android/kernel/common$ make

    编译成功后,就可以在hello 目录下看到hello.o 文件了,这时候编译出来的zImage 已经包含了hello 驱动。

    十. 参照在Ubuntu 上下载、编译和安装Android 最新内核源代码(Linux Kernel)一文所示,运行新编译的内核文件,

    验证hello 驱动程序是否已经正常安装:

    linuxidc@www.linuxdc.com:~/Android$ emulator -kernel ./kernel/common/arch/arm/boot/zImage &

    linuxidc@www.linuxdc.com:~/Android$ adb shell

    进入到dev 目录,可以看到hello 设备文件:

    root@Android:/ # cd dev

    root@Android:/dev # ls

    进入到proc 目录,可以看到hello 文件:

    root@Android:/ # cd proc

    root@Android:/proc # ls

    访问hello 文件的值:

    root@Android:/proc # cat hello

    0

    root@Android:/proc # echo '5' > hello

    root@Android:/proc # cat hello

    5

    进入到sys/class 目录,可以看到hello 目录:

    root@Android:/ # cd sys/class

    root@Android:/sys/class # ls

    进入到hello 目录,可以看到hello 目录:

    root@Android:/sys/class # cd hello

    root@Android:/sys/class/hello # ls

    进入到下一层hello 目录,可以看到val 文件:

    root@Android:/sys/class/hello # cd hello

    root@Android:/sys/class/hello/hello # ls

    访问属性文件val 的值:

    root@Android:/sys/class/hello/hello # cat val

    5

    root@Android:/sys/class/hello/hello # echo '0' > val

    root@Android:/sys/class/hello/hello # cat val

    0

    至此,我们的hello 内核驱动程序就完成了,并且验证一切正常。这里我们采用的是系统提供的方法和驱动程序进行

    交互,也就是通过proc 文件系统和devfs 文件系统的方法,下一篇文章中,我们将通过自己编译的C 语言程序来访问/dev/hello

    文件来和hello 驱动程序交互,敬请期待。

    Android 系统中增加可执行程序来访问硬件驱动程序。

    在前一篇文章http://www.linuxidc.com/Linux/2011-07/38977.htm 中,我们介绍了如何在Ubuntu 上为Android 系统编写Linux

    内核驱动程序。在这个名为hello 的Linux 内核驱动程序中,创建三个不同的文件节点来供用户空间访问,分别是传统的设备

    文件/dev/hello、proc 系统文件/proc/hello 和devfs 系统属性文件/sys/class/hello/hello/val。进一步,还通过cat 命令来直接访

    问/proc/hello 和/sys/class/hello/hello/val 文件来,以验证驱动程序的正确性。在这一篇文章里,我们将通过自己编写的C 可执

    行程序来访问设备文件/dev/hello。可能读者会觉得奇怪,怎么能在Android 系统中用C 语言来编写应用程序呢?Android 系统

    上的应用程序不都是Java 应用程序吗?其实是可以的,读者不妨用adb shell 命令连上Android 模拟器,在/system/bin 目录下

    可以看到很多C 可执行程序,如cat 命令。

     

    今天,我们就来学习一下怎么在Android 系统中添加用C 语言编写的可执行程序吧。

     

    一. 参照在Ubuntu 上为Android 系统编写Linux 内核驱动程序一文,准备好Linux 驱动程序。使用Android 模拟器加载

    包含这个Linux 驱动程序的内核文件,并且使用adb shell 命令连接上模拟,验证在/dev 目录中存在设备文件hello。

     

    二. 进入到Android 源代码工程的external 目录,创建hello 目录:

    linuxidc@www.linuxidc.com:~/Android$ cd external

    linuxidc@www.linuxidc.com:~/Android/external$ mkdir hello

     

    三. 在hello 目录中新建hello.c 文件:

    1.#include <stdio.h>

    2.#include <stdlib.h>

    3.#include <fcntl.h>

    4.#define DEVICE_NAME "/dev/hello"

    5.int main(int argc, char** argv)

    6.{

    7. int fd = -1;

    8. int val = 0;

    9. fd = open(DEVICE_NAME, O_RDWR);

    10. if(fd == -1) {

    11. printf("Failed to open device %s./n", DEVICE_NAME);

    12. return -1;

    13. }

    14.

    15. printf("Read original value:/n");

    16. read(fd, &val, sizeof(val));

    17. printf("%d./n/n", val);

    18. val = 5;

    19. printf("Write value %d to %s./n/n", val, DEVICE_NAME);

    20. write(fd, &val, sizeof(val));

    21.

    22. printf("Read the value again:/n");

    23. read(fd, &val, sizeof(val));

    24. printf("%d./n/n", val);

    25. close(fd);

    26. return 0;

    27.}

    这个程序的作用中,打开/dev/hello 文件,然后先读出/dev/hello 文件中的值,接着写入值5 到/dev/hello 中去,最后再

    次读出/dev/hello 文件中的值,看看是否是我们刚才写入的值5。从/dev/hello 文件读写的值实际上就是我们虚拟的硬件的寄

    存器val 的值。

     

    四. 在hello 目录中新建Android.mk 文件:

    LOCAL_PATH := $(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_MODULE_TAGS := optional

    LOCAL_MODULE := hello

    LOCAL_SRC_FILES := $(call all-subdir-c-files)

    include $(BUILD_EXECUTABLE)

    注意,BUILD_EXECUTABLE 表示我们要编译的是可执行程序。

    五. 参照如何单独编译Android 源代码中的模块一文,使用mmm 命令进行编译:

    linuxidc@www.linuxidc.com:~/Android$ mmm ./external/hello

    编译成功后,就可以在out/target/product/gerneric/system/bin 目录下,看到可执行文件hello 了。

     

    六. 重新打包Android 系统文件system.img:

    linuxidc@www.linuxidc.com:~/Android$ make snod

    这样,重新打包后的system.img 文件就包含刚才编译好的hello 可执行文件了。

     

    七. 运行模拟器,使用/system/bin/hello 可执行程序来访问Linux 内核驱动程序:

    linuxidc@www.linuxidc.com:~/Android$ emulator -kernel ./kernel/common/arch/arm/boot/zImage &

    linuxidc@www.linuxidc.com:~/Android$ adb shell

    root@Android:/ # cd system/bin

    root@Android:/system/bin # ./hello

    Read the original value:

    0.

    Write value 5 to /dev/hello.

    Read the value again:

    5.

    看到这个结果,就说我们编写的C 可执行程序可以访问我们编写的Linux 内核驱动程序了。

    介绍完了如何使用C 语言编写的可执行程序来访问我们的Linux 内核驱动程序,读者可能会问,能不能在Android 的

    Application Frameworks 提供Java 接口来访问Linux 内核驱动程序呢?可以的,接下来的几篇文章中,我们将介绍如何在Android

    的Application Frameworks 中,增加Java 接口来访问Linux 内核驱动程序,敬请期待。

    Ubuntu 上为Android 增加硬件抽象层(HAL)模块访问Linux 内核驱动程序

    在Android 硬件抽象层(HAL)概要介绍和学习计划一文中,我们简要介绍了在Android 系统为为硬件编写驱动程序的方法。

    简单来说,硬件驱动程序一方面分布在Linux 内核中,另一方面分布在用户空间的硬件抽象层中。接着,在Ubuntu 上为Android

    系统编写Linux 内核驱动程序

    一文中举例子说明了如何在Linux 内核编写驱动程序。在这一篇文章中,我们将继续介绍Android 系统硬件驱动程序的另一

    方面实现,即如何在硬件抽象层中增加硬件模块来和内核驱动程序交互。在这篇文章中,我们还将学习到如何在Android 系

    统创建设备文件时用类似Linux 的udev 规则修改设备文件模式的方法。

    一. 参照在Ubuntu 上为Android 系统编写Linux 内核驱动程序一文所示,准备好示例内核驱动序。完成这个内核驱动程序后,

    便可以在Android 系统中得到三个文件,分别是/dev/hello、/sys/class/hello/hello/val 和/proc/hello。在本文中,我们将通过设

    备文件/dev/hello 来连接硬件抽象层模块和Linux 内核驱动程序模块。

    二. 进入到在hardware/libhardware/include/hardware 目录,新建hello.h 文件:

    linuxidc@www.linuxidc.com:~/Android$ cd hardware/libhardware/include/hardware

    linuxidc@www.linuxidc.com:~/Android/hardware/libhardware/include/hardware$ vi hello.h

    hello.h 文件的内容如下:

    1.#ifndef Android_HELLO_INTERFACE_H

    2.#define ANDROID_HELLO_INTERFACE_H

    3.#include <hardware/hardware.h>

    4.

    5.__BEGIN_DECLS

    6.

    7./*定义模块ID*/

    8.#define HELLO_HARDWARE_MODULE_ID "hello"

    9.

    10./*硬件模块结构体*/

    11.struct hello_module_t {

    12. struct hw_module_t common;

    13.};

    14.

    15./*硬件接口结构体*/

    16.struct hello_device_t {

    17. struct hw_device_t common;

    18. int fd;

    19. int (*set_val)(struct hello_device_t* dev, int val);

    20. int (*get_val)(struct hello_device_t* dev, int* val);

    21.};

    22.

    23.__END_DECLS

    24.

    25.#endif 这里按照Android 硬件抽象层规范的要求,分别定义模块ID、模块结构体以及硬件接口结构体。在硬件接口结构

    体中,fd 表示设备文件描述符,对应我们将要处理的设备文件"/dev/hello",set_val 和get_val 为该HAL 对上提供的函数接口。

    三. 进入到hardware/libhardware/modules 目录,新建hello 目录,并添加hello.c 文件。 hello.c 的内容较多,我们分段来看。

    首先是包含相关头文件和定义相关结构:

    1.#define LOG_TAG "HelloStub"

    2.

    3.#include <hardware/hardware.h>

    4.#include <hardware/hello.h>

    5.#include <fcntl.h>

    6.#include <errno.h>

    7.#include <cutils/log.h>

    8.#include <cutils/atomic.h>

    9.

    10.#define DEVICE_NAME "/dev/hello"

    11.#define MODULE_NAME "Hello"

    12.#define MODULE_AUTHOR "shyluo@gmail.com"

    13.

    14./*设备打开和关闭接口*/

    15.static int hello_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device);

    16.static int hello_device_close(struct hw_device_t* device);

    17.

    18./*设备访问接口*/

    19.static int hello_set_val(struct hello_device_t* dev, int val);

    20.static int hello_get_val(struct hello_device_t* dev, int* val);

    21.

    22./*模块方法表*/

    23.static struct hw_module_methods_t hello_module_methods = {

    24. open: hello_device_open

    25.};

    26.

    27./*模块实例变量*/

    28.struct hello_module_t HAL_MODULE_INFO_SYM = {

    29. common: {

    30. tag: HARDWARE_MODULE_TAG,

    31. version_major: 1,

    32. version_minor: 0,

    33. id: HELLO_HARDWARE_MODULE_ID,

    34. name: MODULE_NAME,

    35. author: MODULE_AUTHOR,

    36. methods: &hello_module_methods,

    37. }

    38.};

    这里,实例变量名必须为HAL_MODULE_INFO_SYM,tag 也必须为HARDWARE_MODULE_TAG,这是Android 硬件抽象层规范规

    定的。

    定义hello_device_open 函数:

    1.static int hello_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device) {

    2. struct hello_device_t* dev;dev = (struct hello_device_t*)malloc(sizeof(struct hello_device_t));

    2.

    3. if(!dev) {

    4. LOGE("Hello Stub: failed to alloc space");

    5. return -EFAULT;

    6. }

    7.

    8. memset(dev, 0, sizeof(struct hello_device_t));

    9. dev->common.tag = HARDWARE_DEVICE_TAG;

    10. dev->common.version = 0;

    11. dev->common.module = (hw_module_t*)module;

    12. dev->common.close = hello_device_close;

    13. dev->set_val = hello_set_val;dev->get_val = hello_get_val;

    14.

    15. if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {

    16. LOGE("Hello Stub: failed to open /dev/hello -- %s.", strerror(errno));free(dev);

    17. return -EFAULT; 18. }

    19.

    20. *device = &(dev->common);

    21. LOGI("Hello Stub: open /dev/hello successfully.");

    22.

    23. return 0;

    24.}

    DEVICE_NAME 定义为"/dev/hello"。由于设备文件是在内核驱动里面通过device_create 创建的,而device_create 创建的设备文

    件默认只有root 用户可读写,而hello_device_open 一般是由上层APP 来调用的,这些APP 一般不具有root 权限,这时候就

    导致打开设备文件失败:

    Hello Stub: failed to open /dev/hello -- Permission denied.

    解决办法是类似于Linux 的udev 规则,打开Android 源代码工程目录下,进入到system/core/rootdir 目录,里面有一个名为

    uevent.rc 文件,往里面添加一行:

    /dev/hello 0666 root root

    定义hello_device_close、hello_set_val 和hello_get_val 这三个函数:

    1.static int hello_device_close(struct hw_device_t* device) {

    2. struct hello_device_t* hello_device = (struct hello_device_t*)device;

    3.

    4. if(hello_device) {

    5. close(hello_device->fd);

    6. free(hello_device);

    7. }

    8.

    9. return 0;

    10.}

    11.

    12.static int hello_set_val(struct hello_device_t* dev, int val) {

    13. LOGI("Hello Stub: set value %d to device.", val);

    14.

    15. write(dev->fd, &val, sizeof(val));

    16.

    17. return 0;

    18.}

    19.

    20.static int hello_get_val(struct hello_device_t* dev, int* val) {

    21. if(!val) {

    22. LOGE("Hello Stub: error val pointer");

    23. return -EFAULT;

    24. }

    25.

    26. read(dev->fd, val, sizeof(*val));

    27.

    28. LOGI("Hello Stub: get value %d from device", *val);

    29.

    30. return 0;

    31.}

    四. 继续在hello 目录下新建Android.mk 文件:

    LOCAL_PATH := $(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_MODULE_TAGS := optional

    LOCAL_PRELINK_MODULE := false

    LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw

    LOCAL_SHARED_LIBRARIES := liblog

    LOCAL_SRC_FILES := hello.c

    LOCAL_MODULE := hello.default

    include $(BUILD_SHARED_LIBRARY)

    注意,LOCAL_MODULE 的定义规则,hello 后面跟有default,hello.default 能够保证我们的模块总能被硬象抽象层加载到。

    五. 编译:

    USER-NAME@MACHINE-NAME:~/Android$ mmm hardware/libhardware/moudles/hello

    编译成功后,就可以在out/target/product/generic/system/lib/hw 目录下看到hello.default.so 文件了。

    六. 重新打包Android 系统镜像system.img:

    USER-NAME@MACHINE-NAME:~/Android$ make snod

    重新打包后,system.img 就包含我们定义的硬件抽象层模块hello.default 了。

    虽然我们在Android 系统为我们自己的硬件增加了一个硬件抽象层模块,但是现在Java 应用程序还不能访问到我们的

    硬件。我们还必须编写JNI 方法和在Android 的Application Frameworks 层增加API 接口,才能让上层Application 访问我们的

    硬件。在接下来的文章中,我们还将完成这一系统过程,使得我们能够在Java 应用程序中访问我们自己定制的硬件。

    Android 硬件抽象层(HAL)模块编写JNI 方法提供Java 访问硬件服务接口

    在上两篇文章中(http://www.linuxidc.com/Linux/2011-07/38978.htm 与 http://www.linuxidc.com/Linux/2011-07/38980.htm),我

    们介绍了如何为Android 系统的硬件编写驱动程序,包括如何在Linux 内核空间实现内核驱动程序和在用户空间实现硬件抽象

    层接口。实现这两者的目的是为了向更上一层提供硬件访问接口,即为Android 的Application Frameworks 层提供硬件服务。

    我们知道,Android 系统的应用程序是用Java 语言编写的,而硬件驱动程序是用C 语言来实现的,那么,Java 接口如何去访

    问C 接口呢?众所周知,Java 提供了JNI 方法调用,同样,在Android 系统中,Java 应用程序通过JNI 来调用硬件抽象层接口。

    在这一篇文章中,我们将介绍如何为Android 硬件抽象层接口编写JNI 方法,以便使得上层的Java 应用程序能够使用下层提

    供的硬件服务。

    一. 参照在Ubuntu 上为Android 增加硬件抽象层(HAL)模块访问Linux 内核驱动程序一文,准备好硬件抽象层模块,确保

    Android 系统镜像文件system.img 已经包含hello.default 模块。

    二. 进入到frameworks/base/services/jni 目录,新建com_Android_server_HelloService.cpp 文件:

    linuxidc@www.linuxidc.com:~/Android$ cd frameworks/base/services/jni

    linuxidc@www.linuxidc.com:~/Android/frameworks/base/services/jni$ vi com_android_server_HelloService.cpp

    在com_Android_server_HelloService.cpp 文件中,实现JNI 方法。注意文件的命令方法,com_android_server 前缀表示的是包

    名,表示硬件服务HelloService 是放在frameworks/base/services/java 目录下的com/android/server 目录的,即存在一个命令为

    com.android.server.HelloService 的类。这里,我们暂时略去HelloService 类的描述,在下一篇文章中,我们将回到HelloService

    类来。简单地说,HelloService 是一个提供Java 接口的硬件访问服务类。

    首先是包含相应的头文件:

    1.#define LOG_TAG "HelloService"

    2.#include "jni.h"

    3.#include "JNIHelp.h"

    4.#include "Android_runtime/AndroidRuntime.h"

    5.#include <utils/misc.h>

    6.#include <utils/Log.h>

    7.#include <hardware/hardware.h>

    8.#include <hardware/hello.h>

    9.#include <stdio.h> 接着定义hello_init、hello_getVal 和hello_setVal 三个JNI 方法:

    1.namespace Android

    2.{

    3. /*在硬件抽象层中定义的硬件访问结构体,参考<hardware/hello.h>*/

    4. struct hello_device_t* hello_device = NULL;

    5. /*通过硬件抽象层定义的硬件访问接口设置硬件寄存器val 的值*/

    6. static void hello_setVal(JNIEnv* env, jobject clazz, jint value) {

    7. int val = value;

    8. LOGI("Hello JNI: set value %d to device.", val);

    9. if(!hello_device) {

    10. LOGI("Hello JNI: device is not open.");

    11. return;

    12. }

    13.

    14. hello_device->set_val(hello_device, val);

    15. }

    16. /*通过硬件抽象层定义的硬件访问接口读取硬件寄存器val 的值*/

    17. static jint hello_getVal(JNIEnv* env, jobject clazz) {

    18. int val = 0;

    19. if(!hello_device) {

    20. LOGI("Hello JNI: device is not open.");

    21. return val;

    22. }

    23. hello_device->get_val(hello_device, &val);

    24.

    25. LOGI("Hello JNI: get value %d from device.", val);

    26.

    27. return val;

    28. }

    29. /*通过硬件抽象层定义的硬件模块打开接口打开硬件设备*/

    30. static inline int hello_device_open(const hw_module_t* module, struct hello_device_t** device) {

    31. return module->methods->open(module, HELLO_HARDWARE_MODULE_ID, (struct hw_device_t**)device);

    32. }

    33. /*通过硬件模块ID 来加载指定的硬件抽象层模块并打开硬件*/

    34. static jboolean hello_init(JNIEnv* env, jclass clazz) {

    35. hello_module_t* module;

    36.

    37. LOGI("Hello JNI: initializing......");

    38. if(hw_get_module(HELLO_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) {

    39. LOGI("Hello JNI: hello Stub found.");

    40. if(hello_device_open(&(module->common), &hello_device) == 0) {

    41. LOGI("Hello JNI: hello device is open.");

    42. return 0;

    43. }

    44. LOGE("Hello JNI: failed to open hello device.");

    45. return -1;

    46. }

    47. LOGE("Hello JNI: failed to get hello stub module.");

    48. return -1;

    49. }

    50. /*JNI 方法表*/

    51. static const JNINativeMethod method_table[] = {

    52. {"init_native", "()Z", (void*)hello_init},

    53. {"setVal_native", "(I)V", (void*)hello_setVal},

    54. {"getVal_native", "()I", (void*)hello_getVal},

    55. };

    56. /*注册JNI 方法*/

    57. int register_Android_server_HelloService(JNIEnv *env) {

    58. return jniRegisterNativeMethods(env, "com/Android/server/HelloService", method_table, NELEM(method_table));

    59. }

    60.};

    注意,在hello_init函数中,通过Android 硬件抽象层提供的hw_get_module 方法来加载模块ID为HELLO_HARDWARE_MODULE_ID

    的硬件抽象层模块,其中,HELLO_HARDWARE_MODULE_ID 是在<hardware/hello.h>中定义的。Android 硬件抽象层会根据

    HELLO_HARDWARE_MODULE_ID 的值在Android 系统的/system/lib/hw 目录中找到相应的模块,然后加载起来,并且返回

    hw_module_t 接口给调用者使用。在jniRegisterNativeMethods 函数中,第二个参数的值必须对应HelloService 所在的包的路径,

    即com.android.server.HelloService。

    三. 修改同目录下的onload.cpp 文件,首先在namespace Android 增加register_android_server_HelloService 函数声明:

    namespace Android {

    int register_Android_server_HelloService(JNIEnv *env);

    };

    在JNI_onLoad 增加register_Android_server_HelloService 函数调用:

    extern "C" jint JNI_onLoad(JavaVM* vm, void* reserved)

    {

    register_android_server_HelloService(JNIEnv *env);

    }

    这样,在Android 系统初始化时,就会自动加载该JNI 方法调用表。

    四. 修改同目录下的Android.mk 文件,在LOCAL_SRC_FILES 变量中增加一行:

    LOCAL_SRC_FILES:= /

    com_android_server_AlarmManagerService.cpp /

    com_android_server_BatteryService.cpp /

    com_android_server_InputManager.cpp /

    com_android_server_LightsService.cpp /

    com_android_server_PowerManagerService.cpp /

    com_android_server_SystemServer.cpp /

    com_android_server_UsbService.cpp /

    com_android_server_VibratorService.cpp /

    com_android_server_location_GpsLocationProvider.cpp /

    com_android_server_HelloService.cpp /

    onload.cpp

    五. 编译和重新找亿system.img:

    USER-NAME@MACHINE-NAME:~/Android$ mmm frameworks/base/services/jni

    USER-NAME@MACHINE-NAME:~/Android$ make snod

    这样,重新打包的system.img 镜像文件就包含我们刚才编写的JNI 方法了,也就是我们可以通过Android 系统的

    Application Frameworks 层提供的硬件服务HelloService 来调用这些JNI 方法,进而调用低层的硬件抽象层接口去访问硬件了。

    前面提到, 在这篇文章中, 我们暂时忽略了HelloService 类的实现, 在下一篇文章中

    http://www.linuxidc.com/Linux/2011-07/38982.htm,我们将描述如何实现硬件服务HelloService。

    Ubuntu 上为Android 系统的Application Frameworks 层增加硬件访问服务

    在数字科技日新月异的今天,软件和硬件的完美结合,造就了智能移动设备的流行。今天大家对iOS 和Android 系统的趋之若

    鹜,一定程度上是由于这两个系统上有着丰富多彩的各种应用软件。因此,软件和硬件的关系,在一定程度上可以说,硬件

    是为软件服务的。硬件工程师研发出一款硬件设备,自然少了软件工程师为其编写驱动程序;而驱动程序的最终目的,是为

    了使得最上层的应用程序能够使用这些硬件提供的服务来为用户提供软件功能。对Android 系统上的应用软件来说,就是要

    在系统的Application Frameworks 层为其提供硬件服务。在前面的几篇文章中,我们着重介绍了Linux 内核层、硬件抽象层和

    运行时库层提供的自定义硬件服务接口,这些接口都是通过C 或者C++语言来实现的。在这一篇文章中,我们将介绍如何在

    Android 系统的Application Frameworks 层提供Java 接口的硬件服务。

    一. 参照在Ubuntu 为Android 硬件抽象层(HAL)模块编写JNI 方法提供Java 访问硬件服务接口一文所示,为硬件抽象层模块

    准备好JNI 方法调用层。

    二. 在Android 系统中,硬件服务一般是运行在一个独立的进程中为各种应用程序提供服务。因此,调用这些硬件服务的应用

    程序与这些硬件服务之间的通信需要通过代理来进行。为此, 我们要先定义好通信接口。进入到

    frameworks/base/core/java/android/os 目录,新增IHelloService.aidl 接口定义文件:

    linuxidc@www.linuxidc.com:~/Android$ cd frameworks/base/core/java/android/os

    linuxidc@www.linuxidc.com:~/Android/frameworks/base/core/java/android/os$ vi IHelloService.aidl

    IHelloService.aidl 定义了IHelloService 接口:

    1.package Android.os;

    2.

    3.interface IHelloService {

    4. void setVal(int val);

    5. int getVal(); 6.}

    IHelloService 接口主要提供了设备和获取硬件寄存器val 的值的功能,分别通过setVal 和getVal 两个函数来实现。

    三.返回到frameworks/base 目录,打开Android.mk 文件,修改LOCAL_SRC_FILES 变量的值,增加IHelloService.aidl 源文件:

    ## READ ME: ########################################################

    ##

    ## When updating this list of aidl files, consider if that aidl is

    ## part of the SDK API. If it is, also add it to the list below that

    ## is preprocessed and distributed with the SDK. This list should

    ## not contain any aidl files for parcelables, but the one below should

    ## if you intend for 3rd parties to be able to send those objects

    ## across process boundaries.

    ##

    ## READ ME: ########################################################

    LOCAL_SRC_FILES += /

    core/java/Android/os/IVibratorService.aidl /

    core/java/Android/os/IHelloService.aidl /

    core/java/Android/service/urlrenderer/IUrlRendererService.aidl /

    四. 编译IHelloService.aidl 接口:

    linuxidc@www.linuxidc.com:~/Android$ mmm frameworks/base

    这样,就会根据IHelloService.aidl 生成相应的IHelloService.Stub 接口。

    五.进入到frameworks/base/services/java/com/Android/server 目录,新增HelloService.java 文件:

    1.package com.Android.server;

    2.import Android.content.Context;

    2.import Android.os.IHelloService;

    3.import Android.util.Slog;

    4.public class HelloService extends IHelloService.Stub {

    5. private static final String TAG = "HelloService";

    6. HelloService() {

    7. init_native();

    8. }

    9. public void setVal(int val) {

    10. setVal_native(val);

    11. }

    12. public int getVal() {

    13. return getVal_native();

    14. }

    15.

    16. private static native boolean init_native();

    17. private static native void setVal_native(int val);

    18. private static native int getVal_native();

    19.};

    HelloService 主要是通过调用JNI 方法init_native、setVal_native 和getVal_native(见在Ubuntu 为Android 硬件抽象层(HAL)模

    块编写JNI 方法提供Java 访问硬件服务接口一文)来提供硬件服务。

    六. 修改同目录的SystemServer.java 文件,在ServerThread::run 函数中增加加载HelloService 的代码:

    @Override

    public void run() {

    try {

    Slog.i(TAG, "DiskStats Service");

    ServiceManager.addService("diskstats", new DiskStatsService(context));

    } catch (Throwable e) {

    Slog.e(TAG, "Failure starting DiskStats Service", e);

    }

    try {

    Slog.i(TAG, "Hello Service");

    ServiceManager.addService("hello", new HelloService());

    } catch (Throwable e) {

    Slog.e(TAG, "Failure starting Hello Service", e);

    }

    }

    七. 编译HelloService 和重新打包system.img:

    linuxidc@www.linuxidc.com:~/Android$ mmm frameworks/base/services/java

    linuxidc@www.linuxidc.com:~/Android$ make snod

    这样,重新打包后的system.img 系统镜像文件就在Application Frameworks 层中包含了我们自定义的硬件服务HelloService

    了,并且会在系统启动的时候,自动加载HelloService。这时,应用程序就可以通过Java 接口来访问Hello 硬件服务了。我们

    将 在下一篇文章http://www.linuxidc.com/Linux/2011-07/38983.htm中描述如何编写一个Java 应用程序来调用这个HelloService

    接口来访问硬件。

    Android 系统内置Java 应用程序测试Application Frameworks 层的硬件服务

    我们在Android 系统增加硬件服务的目的是为了让应用层的APP 能够通过Java 接口来访问硬件服务。那么, APP 如何通过Java

    接口来访问Application Frameworks 层提供的硬件服务呢?在这一篇文章中,我们将在Android 系统的应用层增加一个内置的

    应用程序,这个内置的应用程序通过ServiceManager 接口获取指定的服务,然后通过这个服务来获得硬件服务。

    一. 参照在Ubuntu 上为Android 系统的Application Frameworks 层增加硬件访问服务

    一文,在Application Frameworks 层定义好自己的硬件服务HelloService,并提供IHelloService 接口提供访问服务。

    二. 为了方便开发,我们可以在IDE 环境下使用Android SDK 来开发Android 应用程序。开发完成后,再把程序源代码移植到

    Android 源代码工程目录中。使用Eclipse 的Android 插件ADT 创建Android 工程很方便,这里不述,可以参考网上其它资料。

    工程名称为Hello,下面主例出主要文件:

    主程序是src/shy/luo/hello/Hello.java:

    1.package shy.luo.hello;

    2.

    3.import shy.luo.hello.R;

    4.import Android.app.Activity;

    5.import Android.os.ServiceManager;

    6.import Android.os.Bundle;

    7.import Android.os.IHelloService;

    8.import Android.os.RemoteException;

    9.import Android.util.Log;

    10.import Android.view.View;

    11.import Android.view.View.OnClickListener;

    12.import Android.widget.Button;

    13.import Android.widget.EditText;

    14.

    15.public class Hello extends Activity implements OnClickListener {

    16. private final static String LOG_TAG = "shy.luo.renju.Hello";

    17.

    18. private IHelloService helloService = null;

    19.

    20. private EditText valueText = null;

    21. private Button readButton = null;

    22. private Button writeButton = null;

    23. private Button clearButton = null;

    24.

    25. /** Called when the activity is first created. */

    26. @Override

    27. public void onCreate(Bundle savedInstanceState) {

    28. super.onCreate(savedInstanceState);

    29. setContentView(R.layout.main);

    30.

    31. helloService = IHelloService.Stub.asInterface(

    32. ServiceManager.getService("hello"));

    33.

    34. valueText = (EditText)findViewById(R.id.edit_value);

    35. readButton = (Button)findViewById(R.id.button_read);

    36. writeButton = (Button)findViewById(R.id.button_write);

    37. clearButton = (Button)findViewById(R.id.button_clear);

    38.

    39. readButton.setOnClickListener(this);

    40. writeButton.setOnClickListener(this);

    41. clearButton.setOnClickListener(this);

    42.

    43. Log.i(LOG_TAG, "Hello Activity Created");

    44. }

    45.

    46. @Override

    47. public void onClick(View v) {

    48. if(v.equals(readButton)) {

    49. try {

    50. int val = helloService.getVal();

    51. String text = String.valueOf(val);

    52. valueText.setText(text);

    53. } catch (RemoteException e) {

    54. Log.e(LOG_TAG, "Remote Exception while reading value from device.");

    55. }

    56. }

    57. else if(v.equals(writeButton)) {

    58. try {

    59. String text = valueText.getText().toString();

    60. int val = Integer.parseInt(text);

    61. helloService.setVal(val);

    62. } catch (RemoteException e) {

    63. Log.e(LOG_TAG, "Remote Exception while writing value to device.");

    64. }

    65. }

    66. else if(v.equals(clearButton)) {

    67. String text = "";

    68. valueText.setText(text);

    69. }

    70. }

    71.}

    程序通过ServiceManager.getService("hello")来获得HelloService,接着通过IHelloService.Stub.asInterface 函数转换为IHelloService

    接口。其中,服务名字“hello”是系统启动时加载HelloService 时指定的,而IHelloService 接口定义在Android.os.IHelloService

    中,具体可以参考在Ubuntu 上为Android 系统的Application Frameworks 层增加硬件访问服务一文。这个程序提供了简单的读

    定自定义硬件有寄存器val 的值的功能,通过IHelloService.getVal 和IHelloService.setVal 两个接口实现。

    界面布局文件res/layout/main.xml:

    1.<?xml version="1.0" encoding="utf-8"?>

    2. <LinearLayout xmlns:Android="http://schemas.android.com/apk/res/android"

    3. Android:orientation="vertical"

    4. Android:layout_width="fill_parent"

    5. Android:layout_height="fill_parent">

    6. <LinearLayout

    7. Android:layout_width="fill_parent"

    8. Android:layout_height="wrap_content"

    9. Android:orientation="vertical"

    10. Android:gravity="center">

    11. <TextView

    12. Android:layout_width="wrap_content"

    13. Android:layout_height="wrap_content"

    14. Android:text="@string/value">

    15. </TextView>

    16. <EditText

    17. Android:layout_width="fill_parent"

    18. Android:layout_height="wrap_content"

    19. Android:id="@+id/edit_value"

    20. Android:hint="@string/hint">

    21. </EditText>

    22. </LinearLayout>

    23. <LinearLayout

    24. Android:layout_width="fill_parent"

    25. Android:layout_height="wrap_content"

    26. Android:orientation="horizontal"

    27. Android:gravity="center">

    28. <Button

    29. Android:id="@+id/button_read"

    30. Android:layout_width="wrap_content"

    31. Android:layout_height="wrap_content"

    32. Android:text="@string/read">

    33. </Button>

    34. <Button

    35. Android:id="@+id/button_write"

    36. Android:layout_width="wrap_content"

    37. Android:layout_height="wrap_content"

    38. Android:text="@string/write">

    39. </Button>

    40. <Button

    41. Android:id="@+id/button_clear"

    42. Android:layout_width="wrap_content"

    43. Android:layout_height="wrap_content"

    44. Android:text="@string/clear">

    45. </Button>

    46. </LinearLayout>

    47. </LinearLayout> 字符串文件res/values/strings.xml:

    1.<?xml version="1.0" encoding="utf-8"?>

    2. <resources>

    3. <string name="app_name">Hello</string>

    4. <string name="value">Value</string>

    5. <string name="hint">Please input a value...</string>

    6. <string name="read">Read</string>

    7. <string name="write">Write</string>

    8. <string name="clear">Clear</string>

    9. </resources> 程序描述文件AndroidManifest.xml:

    1.<?xml version="1.0" encoding="utf-8"?>

    2. <manifest xmlns:Android="http://schemas.android.com/apk/res/android"

    3. package="shy.luo.hello"

    4. Android:versionCode="1"

    5. Android:versionName="1.0">

    6. <application Android:icon="@drawable/icon" android:label="@string/app_name">

    7. <activity Android:name=".Hello"

    8. Android:label="@string/app_name">

    9. <intent-filter>

    10. <action Android:name="android.intent.action.MAIN" />

    11. <category Android:name="android.intent.category.LAUNCHER" />

    12. </intent-filter>

    13. </activity>

    14. </application>

    15. <uses-sdk

    16. Android:minSdkVersion="7"

    17. Android:targetSdkVersion="7">

    18. </uses-sdk>

    19. </manifest>

    三. 将Hello 目录拷贝至packages/experimental 目录,新增Android.mk 文件:

    linuxidc@www.linuxidc.com:~/Android/packages/experimental$ vi Android.mkAndroid.mk 的文件内容如下:

    LOCAL_PATH:= $(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_MODULE_TAGS := optional

    LOCAL_SRC_FILES := $(call all-subdir-java-files)

    LOCAL_PACKAGE_NAME := Hello

    include $(BUILD_PACKAGE)

    四. 编译:

    linuxidc@www.linuxidc.com:~/Android$ mmm packages/experimental/Hello

    编译成功后,便可以在out/target/product/generic/system/app 目录下看到Hello.apk 文件了。

    五. 重新打包系统镜像文件system.img:

    linuxidc@www.linuxidc.com:~/Android$ make snod

    重新打包后的system.img 文件就内置了Hello.apk 文件了。

    六. 运行Android 模拟器:

    linuxidc@www.linuxidc.com:~/Android$ emulator -kernel kernel/common/arch/arm/boot/zImage &

    在Home Screen 中可以看到Hello 应用程序:

    打开Hello 应用程序:

    点击Read 按钮,可以从HelloService 中读取硬件寄存器val 的值;点击Clear 按钮,可以清空文本框的值;在文本框中输入一

    个数值,再点击Write 按钮,便可以将这个值写入到硬件寄存器val 中去,可以再次点击Read 按钮来验证是否正确写入了值。

    至此,我们就完整地学习了在Android 的Linux 内核空间添加硬件驱动程序、在Android 的硬件抽象层添加硬件接口、在Android

    的Application Frameworks 层提供硬件服务以及在Android 的应用层调用硬件服务的整个过程了,希望能为读者进入Android

    系统提供入门帮助。重新学习整个过程,请参考Android 硬件抽象层(HAL)概要介绍和学习计划。

    本篇文章来源于 Linux 公社网站,原文链接:http://www.linuxidc.com/Linux/2011-07/38980.htm__

    展开全文
  • android驱动开发入门

    2018-09-02 11:55:22
    欢迎使用Markdown编辑器写博客 本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦: Markdown和扩展Markdown简洁的语法 代码块高亮 图片链接和图片上传 LaTex数学公式 ...

    欢迎使用Markdown编辑器写博客

    本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦:

    • Markdown和扩展Markdown简洁的语法
    • 代码块高亮
    • 图片链接和图片上传
    • LaTex数学公式
    • UML序列图和流程图
    • 离线写博客
    • 导入导出Markdown文件
    • 丰富的快捷键

    快捷键

    • 加粗 Ctrl + B
    • 斜体 Ctrl + I
    • 引用 Ctrl + Q
    • 插入链接 Ctrl + L
    • 插入代码 Ctrl + K
    • 插入图片 Ctrl + G
    • 提升标题 Ctrl + H
    • 有序列表 Ctrl + O
    • 无序列表 Ctrl + U
    • 横线 Ctrl + R
    • 撤销 Ctrl + Z
    • 重做 Ctrl + Y

    Markdown及扩展

    Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]

    使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。

    本编辑器支持 Markdown Extra ,  扩展了很多好用的功能。具体请参考Github.

    表格

    Markdown Extra 表格语法:

    项目 价格
    Computer $1600
    Phone $12
    Pipe $1

    可以使用冒号来定义对齐方式:

    项目 价格 数量
    Computer 1600 元 5
    Phone 12 元 12
    Pipe 1 元 234

    定义列表

    Markdown Extra 定义列表语法:
    项目1
    项目2
    定义 A
    定义 B
    项目3
    定义 C

    定义 D

    定义D内容

    代码块

    代码块语法遵循标准markdown代码,例如:

    @requires_authorization
    def somefunc(param1='', param2=0):
        '''A docstring'''
        if param1 > param2: # interesting
            print 'Greater'
        return (param2 - param1 + 1) or None
    class SomeClass:
        pass
    >>> message = '''interpreter
    ... prompt'''

    脚注

    生成一个脚注1.

    目录

    [TOC]来生成目录:

    数学公式

    使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.

    • 行内公式,数学公式为:Γ(n)=(n1)!nN
    • 块级公式:

    x=b±b24ac2a

    更多LaTex语法请参考 这儿.

    UML 图:

    可以渲染序列图:

    Created with Raphaël 2.1.2张三张三李四李四嘿,小四儿, 写博客了没?李四愣了一下,说:忙得吐血,哪有时间写。

    或者流程图:

    Created with Raphaël 2.1.2开始我的操作确认?结束yesno
    • 关于 序列图 语法,参考 这儿,
    • 关于 流程图 语法,参考 这儿.

    离线写博客

    即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。

    用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。

    博客发表后,本地缓存将被删除。 

    用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。

    注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱

    浏览器兼容

    1. 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
    2. IE9以下不支持
    3. IE9,10,11存在以下问题
      1. 不支持离线功能
      2. IE9不支持文件导入导出
      3. IE10不支持拖拽文件导入


    1. 这里是 脚注内容.
    展开全文
  • Android底层驱动开发 -驱动配置篇## 所需要的硬件及软件资源 一块Android开发板(我用的是Firefly-AIO-3288J) 开发板的系统源码 一台运行Linux系统的主机(虚拟机也行,但内存配置不要低于16g,否则会编译失败) 准备...
  • 来自于百度下载的一篇文档,Android驱动开发全过程(有图有真相),看了感觉简洁精炼,分享上来
  • 最近开始接触Android底层的开发,这里面将记录开发一个驱动以及到测试的全过程。   首先,需要搭建Android开发环境。我这里采用的是Ubuntu 11.04 64bit的系统,里面使用gcc-4.4,Android 4.0.4源码,硬件是S5PV...
  • 以下文章参考网上搜到的《Android驱动开发全过程(有图有真相)》一文,其中根据自己的实际编写情况作了部分修改,不用作商业用途 前言 意外在网上发现了这扁文章,看后感觉很有必要分享,所以整理并上传,希望大家...
  • 李骏、陈小玉编著的《Android驱动开发与移植实战详解》分为18章,依次讲解了Android系统的基本知识, Linux内核的基本知识,分析了Android系统的源码,深入分析HAL层的基本知识,GoldFish下的驱动、MSM内核和驱动、...
  • Android驱动开发知识储备 Android软件层次结构 (1)操作系统层 显示驱动(Frame Buffer),Flash内存驱动,照相机驱动,音频驱动,WiFi驱动,键盘驱动,蓝牙驱动,Binder IPC驱动,Power Management ,这些都包括...
  • Android驱动开发的一些参考资料,转载过来以后学习学习! Android 开发之 ---- 底层驱动开发(一) 驱动概述  说到 android 驱动是离不开 Linux 驱动的。Android 内核采用的是 Linux2.6 内核 (最近Linux 3.3 ...
  • 如何成为一名优秀的Android驱动程序员?参数如下要求: 一、Android驱动的基础知识 1.Android驱动是基于Linux驱动,强烈推荐阅读Linux Device Driver 3rd版,这本书讲了Linux下设备驱动的基础知识,要求反复细读。 2...
  • Android驱动开发权威指南》 杨柳编著 2014年3月出版。看着出版日期比较新,预览的pdf上,代码还挺多的,就脑袋发热,买了。 现在后悔,不是因为多花了那么50多块的冤枉钱,而是白白浪费了我三个月的时间,不值。...
  • 嵌入式Linux/Android驱动开发揭秘(3)摄像头驱动开发 专题简介:本专题通过对Android手机液晶屏部件的深入剖析,让听众了解液晶屏背后的相关知识,了解液晶屏硬件电路原理、以及Linux、Android系统下液晶屏...
1 2 3 4 5 ... 20
收藏数 66,156
精华内容 26,462
关键字:

android驱动开发