开发第一个Android设备驱动程序

来源:互联网 发布:电磁波 知乎 编辑:程序博客网 时间:2024/06/05 17:10

编写第一个Android驱动程序

什么是驱动程序?有些权威人士说的很好,认为驱动程序即是使对设备的操作更为方便、更为高效、更加有组织,比较接近人类思维方式而已。所以文件操作只是对设备操作的组织和抽象,而设备操作就是对文件操作的最终实现。当然,下面是参考相关资料和自己的理解整理的文档,拿来与大家分享,如有错误的分析,请不吝赐教!!

我们都知道,Android系统内核是基于Linux内核的,所以对于编写Android系统的驱动与编写Linux系统的驱动是一样的实现方式。对于Linux驱动的实现,可以参考相关的书籍,我这里只是简单说明设备驱动在Linux中的不同实现方式,如下图所示:

上面为Linux中的设备驱动分层实现逻辑图,便于理解设备驱动的不同实现方式,从上面我们可以知道由实现应用程序的进程通过“设备文件号”来与打开的文件结构相联系,同时每个文件结构则代表着对应一个已打开设备文件的操作上下文。借助这个操作的上下文,进程使用各个文件的线性逻辑空间来进行文件操作。

对于设备文件,它的逻辑空间与设备的逻辑空间一般是相同的,所以在文件系统层就不需要映射了,只需要从“设备逻辑空间”到“物理设备空间”的一层映射即可;而对于普通文件或称之为“磁盘文件”而言,则需要先按照文件系统的结构和规则映射到”设备逻辑空间“的线性逻辑空间。而后,再由”设备逻辑空间“映射到”设备物理空间“了。下面是一个类似于我们都熟悉的程序”HelloWorld”。在这里,这个HelloWorld只是一个实现和验证在Android系统中实现驱动开发的流程,并没有任何实际的意义,具体看下面的例子。

例子:字符驱动程序(最简单的驱动程序,类似于我们都熟悉的第一个HelloWorld程序,它很好地印证了Linux驱动的开发过程)。

这是个虚拟的4字节寄存器(对于寄存器相关可以参考相关书籍),我们要实现的功能是想这个寄存器中写入一个值,然后再从这个寄存器中取出刚刚写入的值,如果相同就说明功能实现正常了。

具体看下面的代码:

头文件部分:helloworld.h

#ifndef _FAKE_REG_H_

#define _FAKE_REG_H_

//导入相关字符设备头文件

#include <linux/cdev.h> 

#include <linux/semaphore.h>

//自定义目标设备描述变量

#define HELLOWORLD_DEVICE_NODE_NAME  "helloworld"

#define HELLOWORLD_DEVICE_FILE_NAME  "helloworld"

#define HELLOWORLD_DEVICE_PROC_NAME  "helloworld"

#define HELLOWORLD_DEVICE_CLASS_NAME  "helloworld"

//自定义设备结构体对象

struct  helloworld_reg_dev {

int val;

struct semaphore sem;

struct cdev dev;

};

#endif

执行文件部分:helloworld.c

//引用相关必须头文件

#include <linux/init.h>

#include <linux/module.h>

#include <linux/types.h>

#include <linux/fs.h>

#include <linux/proc_fs.h>

#include <linux/device.h>

#include <asm/uaccess.h>

//自定义头文件

#include "helloworld.h"

//主次设备号,通过设备文件名和主设备号可以唯一确定一设备

//次设备号表示同类设备的编号

static int helloworld_major = 0;

static int helloworld_minor = 0;

//定义设备类别和设备变量

static struct class*  helloworld_class = NULL;

static struct fake_reg_dev*  helloworld_dev = NULL;

//定义传统的设备文件操作方法

static int helloworld_open(struct inode* inode, struct file* file);

static int helloworld_release(struct inode* inode, struct file* file);

static ssize_t helloworld_read(struct file* file char __user *buf, size_t count, loff_t* f_pos);

static ssize_t helloworld_write(struct file* file, const char __user *buf, size_t count, loff_t* f_pos);

//传统的设备文件操作方法列表,后面利用这个方法表来实现对设别的操作

static struct file_operations  helloworld_fops = {

        .owner = THIS_MODULE,

        .open = helloworld_open,

        .release = helloworld_release,

        .read = helloworld_read,

        .write = helloworld_write,

};

//devfs文件系统---特殊的树状结构文件系统,优化了传统的文件系统

//下面为该系统的设属性操作方法

static ssize_t helloworld_val_show(struct device* dev, struct device_attribute* attr,  char* buf);

static ssize_t helloworld_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);

//devfs系统的设备属性

static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, helloworld_val_show, helloworld_val_store);

//下面为相关定义方法及系统调用方法的具体实现

static int helloworld_open(struct inode* inode, struct file* file) {

struct fake_helloworld_dev* dev;

dev = container_of(inode->i_cdev, struct fake_helloworld_dev, dev);

file->private_data = dev;//私有文件指针域

return 0;

}

static ssize_t helloworld_read(struct file* file, char __user *buf, size_t count, loff_t* f_pos) {

ssize_t err = 0;

struct fake_helloworld_dev* dev = file->private_data;

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

return -ERESTARTSYS;

}

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

goto out;

}

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

err = -EFAULT;

goto out;

}

err = sizeof(dev->val);

out:

up(&(dev->sem));//同步信号量访问

return err;

}

static ssize_t helloworld_write(struct file* file, const char __user *buf, size_t count, loff_t* f_pos) {

struct fake_helloworld_dev* dev = filp->private_data;

ssize_t err = 0;

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

                return -ERESTARTSYS;

    }

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

           goto out;

    }

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

err = -EFAULT;

goto out;

}

err = sizeof(dev->val);

out:

up(&(dev->sem));

return err;

}

static int helloworld_release(struct inode* inode, struct file* file) {

return 0;

}

//devfs文件系统获取和修改寄存器中val的方法

static ssize_t __helloworld_get_val(struct fake_helloworld_dev* dev, char* buf) {

int val = 0;

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

                return -ERESTARTSYS;

        }

        val = dev->val;

        up(&(dev->sem));

        return snprintf(buf, PAGE_SIZE, "%d\n", val);

}

static ssize_t __helloworld_set_val(struct helloworld_reg_dev* dev, const char* buf, size_t count) {

int val = 0;

    val = simple_strtol(buf, NULL, 10);

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

                return -ERESTARTSYS;

        }

        dev->val = val;

        up(&(dev->sem));

return count;

}

static ssize_t helloworld_val_show(struct device* dev, struct device_attribute* attr, char* buf) {

struct fake_helloworld_dev* hdev = (struct fake_helloworld_dev*)dev_get_drvdata(dev);

        return __helloworld_get_val(hdev, buf);

}

static ssize_t helloworld_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count) {

 struct fake_helloworld_dev* hdev = (struct fake_helloworld_dev*)dev_get_drvdata(dev);

        return __helloworld_set_val(hdev, buf, count);

}

//实现proc文件系统的接口方法

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

if(off > 0) {

*eof = 1;

return 0;

}

return __helloworld_get_val(helloworld_dev, page);

}

static ssize_t helloworld_proc_write(struct file* file, 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 = __helloworld_set_val(helloworld_dev, page, len);

out:

free_page((unsigned long)page);

return err;

}

static void helloworld_create_proc(void) {

struct proc_dir_entry* entry;

entry = create_proc_entry(HELLOWORLD_DEVICE_PROC_NAME, 0, NULL);

if(entry) {

entry->owner = THIS_MODULE;

entry->read_proc = helloworld_proc_read;

entry->write_proc = helloworld_proc_write;

}

}

static void helloworld_remove_proc(void) {

remove_proc_entry(HELLOWORLD_DEVICE_PROC_NAME, NULL);

}

static int  __helloworld_setup_dev(struct fake_helloworld_dev* dev) {

int err;

dev_t devno = MKDEV(helloworld_major, helloworld_minor);

memset(dev, 0, sizeof(struct fake_helloworld_dev));

cdev_init(&(dev->dev), &helloworld_fops);

dev->dev.owner = THIS_MODULE;

dev->dev.ops = &helloworld_fops;

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

if(err) {

return err;

}

init_MUTEX(&(dev->sem));

dev->val = 0;

return 0;

}

static int __init helloworld_init(void) { 

int err = -1;

dev_t dev = 0;

struct device* temp = NULL;

printk(KERN_ALERT"Initializing helloworld device.\n");

err = alloc_chrdev_region(&dev, 0, 1, HELLOWORLD_DEVICE_NODE_NAME);

if(err < 0) {

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

goto fail;

}

helloworld_major = MAJOR(dev);

helloworld_minor = MINOR(dev);

helloworld_dev = kmalloc(sizeof(struct fake_helloworld_dev), GFP_KERNEL);

if(!helloworld_dev) {

err = -ENOMEM;

printk(KERN_ALERT"Failed to alloc helloworld device.\n");

goto unregister;

}

err = __helloworld_setup_dev(helloworld_dev);

if(err) {

printk(KERN_ALERT"Failed to setup helloworld device: %d.\n", err);

goto cleanup;

}

helloworld_class = class_create(THIS_MODULE, HELLOWORLD_DEVICE_CLASS_NAME);

if(IS_ERR(helloworld_class)) {

err = PTR_ERR(helloworld_class);

printk(KERN_ALERT"Failed to create helloworld device class.\n");

goto destroy_cdev;

}

temp = device_create(helloworld_class, NULL, dev, "%s", HELLOWORLD_DEVICE_FILE_NAME);

if(IS_ERR(temp)) {

err = PTR_ERR(temp);

printk(KERN_ALERT"Failed to create helloworld device.\n");

goto destroy_class;

}

err = device_create_file(temp, &dev_attr_val);

if(err < 0) {

printk(KERN_ALERT"Failed to create attribute val of helloworld device.\n");

                goto destroy_device;

}

dev_set_drvdata(temp, helloworld_dev);

helloworld_create_proc();

printk(KERN_ALERT"Succedded to initialize helloworld device.\n");

return 0;

destroy_device:

device_destroy(helloworld_class, dev);

destroy_class:

class_destroy(helloworld_class);

destroy_cdev:

cdev_del(&(helloworld_dev->dev));

cleanup:

kfree(helloworld_dev);

unregister:

unregister_chrdev_region(MKDEV(helloworld_major, helloworld_minor), 1);

fail:

return err;

}

static void __exit helloworld_exit(void) {

dev_t devno = MKDEV(helloworld_major, helloworld_minor);

printk(KERN_ALERT"Destroy helloworld device.\n");

helloworld_remove_proc();

if(helloworld_class) {

device_destroy(helloworld_class, MKDEV(helloworld_major, helloworld_minor));

class_destroy(helloworld_class);

}

if(helloworld_dev) {

cdev_del(&(helloworld_dev->dev));

kfree(helloworld_dev);

}

unregister_chrdev_region(devno, 1);

}

MODULE_LICENSE("GPL");

MODULE_DESCRIPTION("Fake Register Driver");

module_init(helloworld_init);

module_exit(helloworld_exit);

驱动程序编译方式配置文件:Kconfig

config HELLOWORLD

tristate "HelloWorld Register Driver"

default n

This is the helloworld driver for android.

驱动程序编译脚本文件:Makefile

obj-$(CONFIG_HELLOWORLD) += helloworld.o

,在上面的资源文件准备好之后,我们还需要修改内核默认的Kconfig和Makefile文件,目的是让系统在编译的时候能搞编译和找到我们自己编写的驱动文件。修改的内容如下:

Kconfig文件--->位于arch/arm/下,修改内容:打开文件在menu “Device drivers”和endmenu之间添加内容为source “drivers/helloworld/Kconfig” 保存退出。

Makefile文件--->位于drivers/Makefile,修改内容为:打开文件添加内容为:obi-$(CONFIG_HELLOWORLD) += helloworld/即可。

最后,我们就可以执行make menuconfig来配置编译方式了。我们可以选择的编译方式有编译到系统内核中或以模块的方式编译。我这里选择将其潜入到内核编译。

配置完成之后,执行make命令来编译所编写的驱动了。若出现如下提示信息,则表示编译成功了。

提示信息:Kernel arch/arm/boot/zImage is ready 

验证内核驱动程序:

启动虚拟器

--->emulator -kernel kernel/goldfish/arch/arm/boot/zImage &

进入内核

--->adb shell

--->cd dev

--->ls helloworld 显示helloworld 说明找到了编写的helloworld驱动程序了。

继续验证:

--->cd proc

--->cat helloworld 默认寄存器里内容为0

--->echo 1 > helloworld 写入值1到寄存器中

--->cat helloworld 显示为1 表示验证通过

继续验证:

--->cd /sys/class/helloworld/helloworld/

--->cat val显示为1标志正确

--->echo 0 > val

--->cat val 显示为0 验证正确 通过,恭喜,第一个驱动编写成功!

注意:如果选择以模块方式编译的话,则必须先在第一个界面中选择“Enable loadable module support”选项,按”y”键设为true,即表示内核支持动态加载模块。

本人刚创建了一个QQ群,目的是共同研究学习Android,期待兴趣相投的同学加入,申请时,请输入加入理由!!

群号:179914858

 

原创粉丝点击