从事android工作几年时间,功底不是很深,一直围绕这android系统定制移植开发,慢慢的从应用层接触到framework层,在接触到hal,目前从事的工作wifi驱动相关工作。但是没有系统的学习过驱动知识,现在跟着阳光玻璃杯学习一份驱动的简单实现。
字符设备是指在I/O传输过程中以字符为单位进行传输的设备,如键盘,打印机
字符设备驱动主要做如下3件事:
1,定义一个结构体static struct file_operations变量,在里面定义一些设备的打开、关闭、读、写、控制函数
2,在结构体外分别实现结构体中定义的这些函数
3,向内核中注册或删除驱动模块
static struct file_operations myDriver_fops = {
.owner=THIS_MODULE,
.write=myDriver_write,
.read=myDriver_read,
.ioctl=myDriver_ioctl,
.open=myDriver_open,
.release=myDriver_release,
};
此结构体中规定了驱动程序想应用程序提供的操作接口。
实现write, 从应用程序接收数据送到硬件
static ssize_t myDriver_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos){
.....
copy_from_user(&myDriver_Buffer[*f_pos], buf, fill_size); //用于把用户态的数据拷到内核态,实现数据的传送
....
}
实现read, 从硬件读取数据并交给应用程序
static ssize_t myDriver_read(struct file *filp, char *buf, size_t count, loff_t *f_pos){
....
copy_to_user(buf, &myDriver_Buffer[*f_pos], read_size); //用于实现把内核态的数据拷到用户态下
....
}
实现ioctl, 就是为应用程序提供对硬件行为的控制
static int myDriver_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){
switch(cmd){
case MYDRV_IOCTL0:
break;
case MYDRV_IOCTL1:
break;
}
return 0;
}
实现open, 应用程序打开设备时对设备进行初始化
static int myDriver_open(struct inode *inode, struct file *filp){
return 0;
}
实现release, 当应用程序关闭设备时处理设备的关闭操作
static int myDriver_release(struct inode * inode, struct file *filp){
return 0;
}
实现一个简单的字符设备驱动,功能写一个字符串进去,然后再把他读出来。驱动创建/dev/hello节点,/sys/class/hello/hello/val 设备节点, /proc/hello/hello设备节点。/sys/class/hello/hello/val 主要用于快速测试,/dev/hello才是供我们使用的节点。
一,驱动源码
此份依据阳光玻璃杯的代码修改过,已经在电视机顶盒android 7.1.2版本上测试过。这里也是直接贴出代码,方便日后查看学习
一共含有4个文件,hello.c hello.h Kconfig Makefile,此份驱动没有内置到系统源码下面,是新建了一个文件夹,采用编译链的方式
结构如下:
appkernel
├── hello.c
├── hello.h
├── Kconfig
└── Makefile
hello.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<linux/sched.h>
#include<linux/errno.h>
#include<linux/fcntl.h>
#include<linux/poll.h>
#include<linux/seq_file.h>
#include<linux/mutex.h>
#include<linux/workqueue.h>
#include<asm/uaccess.h>
#include<linux/slab.h>
#include<linux/kernel.h>
#include "hello.h"
//主设备和从设备号变量
static int hello_major = 0;
static int hello_minor = 0;
//设备类别和设备变量
static struct class* hello_class = NULL;
static struct hello_test_dev* hello_dev = NULL;
//传统的设备文件操作方法
static int hello_open(struct inode* inode, struct file* filp);
static int hello_release(struct inode* inode, struct file* filp);
static ssize_t hello_read(struct file* filp, char __user* buf, size_t count, loff_t* f_pos);
static ssize_t hello_write(struct file* filp, const char __user* buf, size_t count, loff_t* f_pos);
static int hello_create_proc(void);
static void hello_remove_proc(void);
static ssize_t hello_proc_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos);
static ssize_t hello_proc_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos);
static int proc_seq_open(struct inode *inode, struct file *file);
/**
* 设备文件操作方法表
*/
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
};
static struct file_operations hello_proc_ops={
.owner = THIS_MODULE,
.read = seq_read,
.write = hello_proc_write,
.llseek=seq_lseek,
.release = seq_release,
.open=proc_seq_open,
};
//访问设置属性方法
static ssize_t hello_val_show(struct device * dev, struct device_attribute* attr, char *buf);
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char *buf, size_t count);
//定义设备属性
static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);
/*打开设备方法*/
static int hello_open(struct inode* inode, struct file* filp){
struct hello_test_dev* dev;
/*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/
dev = container_of(inode->i_cdev, struct hello_test_dev, dev);
filp->private_data = dev;
return 0;
}
//设备文件释放时调用
static int hello_release(struct inode* inode, struct file* filp){
return 0;
}
//读设备的寄存器val的值
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos){
ssize_t err = 0;
struct hello_test_dev* dev = filp->private_data;
//同步访问
if(down_interruptible(&(dev->sem))){
return -ERESTARTSYS;
}
if(count < sizeof(dev->val)){
goto out;
}
/*将寄存器val的值拷贝到用户提供的缓冲区*/
if(copy_to_user(buf, dev->val, sizeof(dev->val))){
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
up(&(dev->sem));
return err;
}
//写设备的寄存器值val
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos){
struct hello_test_dev* dev = filp->private_data;
ssize_t err = 0;
//同步访问
if(down_interruptible(&(dev->sem))){
return -ERESTARTSYS;
}
/*将用户提供的缓冲区的值写到设备寄存器去*/
if(copy_from_user(dev->val, buf, count)){
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
up(&(dev->sem));
return err;
}
/**
* 通过devfs文件系统访问方法,把val看成设备的一个属性,通过读写这个属性来对设备进行访问,
* 主要是实现hello_val_show和hello_val_store两个方法,同时定义了两个内部使用的访问val值的方法__hello_get_val和__hello_set_val:
*/
//读取寄存器val的值到缓冲区buf中,内部使用
static ssize_t __hello_get_val(struct hello_test_dev* dev, char * buf){
if(down_interruptible(&(dev->sem))){
return -ERESTARTSYS;
}
up(&(dev->sem));
return snprintf(buf, PAGE_SIZE, "%s\n",dev->val);
}
//写字符串到内存
static ssize_t __hello_set_val(struct hello_test_dev* dev, const char* buf, size_t count){
if (down_interruptible(&(dev->sem)))
{
return -ERESTARTSYS;
}
printk(KERN_ALERT"_hello_set_val.buf: %s count:%zu\n", buf, count);
printk(KERN_ALERT"_hello_set_val.dev->val:%s count:%zu\n",dev->val, count);
strncpy(dev->val, buf, count);
printk(KERN_ALERT"_hello_set_val.dev->val:%s count:%zu\n" ,dev->val, count);
up(&(dev->sem));
return count;
}
//读取设备属性val
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char * buf){
struct hello_test_dev * hdev = (struct hello_test_dev*) dev_get_drvdata(dev);
printk(KERN_ALERT"hello_val_show.\n");
printk(KERN_ALERT"%s \n", hdev->val);
return __hello_get_val(hdev,buf);
}
//写设备属性val
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count){
struct hello_test_dev* hdev = (struct hello_test_dev*) dev_get_drvdata(dev);
printk(KERN_ALERT"hello_val_store.buf:%s count:%zu\n", buf, count);
return __hello_set_val(hdev, buf, count);
}
/**
* 定义通过proc文件系统访问方法,主要实现了hello_proc_read和hello_proc_write两个方法,
* 同时定义了在proc文件系统创建和删除文件的方法hello_create_proc和hello_remove_proc
*/
static char *str = NULL;
static void *my_seq_start(struct seq_file *m, loff_t *pos)
{
if(0== *pos){
++*pos;
return (void *)1;
}
return NULL;
}
static void *my_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
return NULL;
}
static void my_seq_stop(struct seq_file *m, void *v)
{
}
static int my_seq_show(struct seq_file *m, void *v)
{
seq_printf(m, "current kernel time is %llu\n", (unsigned long long )get_jiffies_64());
seq_printf(m, "str is %s\n", str);
return 0;
}
static struct seq_operations my_seq_fops =
{
.start = my_seq_start,
.next = my_seq_next,
.stop = my_seq_stop,
.show = my_seq_show,
};
static int proc_seq_open(struct inode *inode, struct file *file)
{
return seq_open(file, &my_seq_fops);
}
//读取设备寄存器val的值,保存在page缓冲区中
static ssize_t hello_proc_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos){
char * ptr = PDE_DATA(file_inode(filp));
printk(KERN_ALERT"ptr=%s\n",ptr);
printk("proc read\n");
return 0;
}
//把缓冲区的值buff保存到设备寄存器val中去
static ssize_t hello_proc_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos){
char *temp = kzalloc((len+1), GFP_KERNEL);
if(!temp) return -ENOMEM;
printk(KERN_ALERT"proc write\n");
if(copy_from_user(temp,buf, len)){
kfree(temp);
return -EFAULT;
}
kfree(str);
str = temp;
return len;
}
struct proc_dir_entry *entry = NULL;
struct proc_dir_entry *proc_root = NULL;
//创建 /proc/hello文件
static int hello_create_proc(void){
char temp[] = "hello test\0";
proc_root = proc_mkdir("hello", NULL);
if(proc_root == NULL){
printk(KERN_ERR"Can't create /proc/hello directory\n");
return -EFAULT;
}
entry = proc_create_data(HELLO_DEVICE_PROC_NAME, S_IRUGO | S_IWUSR | S_IWGRP, proc_root, &hello_proc_ops, &temp);
if(entry == NULL){
printk(KERN_ERR"Can't create /proc/hello_dir/hello file\n");
return -EFAULT;
}
return 0;
}
//删除 /proc/hello文件
static void hello_remove_proc(void){
remove_proc_entry(HELLO_DEVICE_PROC_NAME, proc_root);
}
//初始化设备
static int __hello_setup_dev(struct hello_test_dev* dev){
int err;
dev_t devno = MKDEV(hello_major, hello_minor);
memset(dev, 0, sizeof(struct hello_test_dev));
cdev_init(&(dev->dev), &hello_fops);
dev->dev.owner = THIS_MODULE;
dev->dev.ops = &hello_fops;
err = cdev_add(&(dev->dev), devno, 1);
if (err)
{
return err;
}
sema_init(&(dev->sem),10);
dev->val = kmalloc(20, GFP_KERNEL);
strncpy(dev->val, "hello", sizeof("hello"));
return 0;
}
//模块加载方法
static int __init hello_init(void){
int err = -1;
dev_t dev = 0;
struct device* temp = NULL;
printk(KERN_ALERT"hello_init.\n");
//动态分配主设备和从设备号
err = alloc_chrdev_region(&dev, 0, 1, HELLO_DEVICE_NODE_NAME);
if(err < 0){
printk(KERN_ALERT"Failed to alloc char dev region.\n");
goto fail;
}
hello_major = MAJOR(dev);
hello_minor = MINOR(dev);
//分配hello设备结构体变量
hello_dev = kmalloc(sizeof(struct hello_test_dev), GFP_KERNEL);
if(!hello_dev){
err = -ENOMEM;
printk(KERN_ALERT"Failed to alloc hello_dev.\n");
goto unregister;
}
//初始化设备
err = __hello_setup_dev(hello_dev);
if(err){
printk(KERN_ALERT"Failed to setup dev:%d\n",err);
goto cleanup;
}
//在/sys/class/目录下创建设备类别目录hello
hello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME);
if(IS_ERR(hello_class)){
err = PTR_ERR(hello_class);
printk(KERN_ALERT"Failed to create hello class.\n");
goto destroy_cdev;
}
//在/dev 目录和 /sys/cleass/hello 目录下分别创建设备文件hello
temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);
if(IS_ERR(temp)){
err = PTR_ERR(temp);
printk(KERN_ALERT"Failed to create hello device.\n");
goto destroy_class;
}
//在 /sys/class/hello/hello 目录下创建属性文件val
err = device_create_file(temp, &dev_attr_val);
if(err < 0){
printk(KERN_ALERT"Failed to create attribute val.\n");
goto destroy_device;
}
dev_set_drvdata(temp, hello_dev);
//创建/proc/hello文件
hello_create_proc();
printk(KERN_ALERT"Succedded to initialize hello device.\n");
return 0;
destroy_device:
device_destroy(hello_class, dev);
destroy_class:
class_destroy(hello_class);
destroy_cdev:
cdev_del(&(hello_dev->dev));
cleanup:
kfree(hello_dev);
unregister:
unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1);
fail:
return err;
}
//模块卸载方法
static void __exit hello_exit(void) {
dev_t devno = MKDEV(hello_major, hello_minor);
printk(KERN_ALERT"hello_exit.\n");
//删除/proc/hello文件
hello_remove_proc();
//销毁设备类别和设备
if(hello_class){
device_destroy(hello_class, MKDEV(hello_major, hello_minor));
class_destroy(hello_class);
}
//删除字符设备和释放设备内存
if(hello_dev){
cdev_del(&(hello_dev->dev));
kfree(hello_dev);
}
if(hello_dev->val != NULL){
kfree(hello_dev->val);
}
//释放设备号
unregister_chrdev_region(devno, 1);
}
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Test Driver");
module_init(hello_init);
module_exit(hello_exit);
hello.h
#ifndef _HELLO_TEST_H_
#define _HELLO_ANDROID_H_
#include <linux/cdev.h>
#include <linux/semaphore.h>
#define HELLO_DEVICE_NODE_NAME "hello"
#define HELLO_DEVICE_FILE_NAME "hello"
#define HELLO_DEVICE_CLASS_NAME "hello"
#define HELLO_DEVICE_PROC_NAME "hello"
/*
*定义字符设备结构体
*/
struct hello_test_dev {
char * val;
struct semaphore sem; //信号量
struct cdev dev; //内嵌的字符设备
};
#endif
Kconfig
config HELLO
tristate "Test Driver"
default n
help
This is the test driver.
Makefile
EXTRA_CFLAGS += -Werror -Wno-unused
EXTRA_CFLAGS += -O1
ARCH:=arm64
CROSS_COMPILE:=aarch64-linux-gnu-
KSRC:=/home/fht/work/chengshi/n-amlogic/out/target/product/p281/obj/KERNEL_OBJ
obj-$(CONFIG_HELLO)+= hello.o
export CONFIG_HELLO = m
all:modules
modules:
$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KSRC) M=$(shell pwd) modules
.PHONY: modules clean
clean:
$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KSRC) M=$(shell pwd) clean
其中 ARCH,CROSS_COMPILE, KSRC 需要根据系统的编译链修改,否则编译出来的ko文件可能导致insmod失败
CROSS_COMPILE 可以使用AOSP/prebuilts/gcc/linux-x86下gcc, KSRC需要先把AOSP整编,然后才能使用对应.config文件
如果有已知kernel的.config文件,可以修改为已知文件的绝对路径
二,编译
以上文件写好后,我们就可以在当前目录下执行make, 编译驱动文件了
hello.c hello.ko hello.mod.c .hello.mod.o.cmd .hello.o.cmd Makefile Module.symvers
hello.h .hello.ko.cmd hello.mod.o hello.o Kconfig modules.order .tmp_versions
hello.ko文件就是我们需要的驱动文件了
三,应用/测试
使用adb push 将hello.ko文件放置到system/lib/modules下,最好是能有已经root过的设备上测试。
adb push hello.ko system/lib/modules
加载刚刚放置进来的hello.ko
adb shell; cd system/lib/modules;
insmod hello.ko 如果没有报错说明加载没问题,我们可以使用lsmod查看已经加载的驱动列表,Module列是否有名称为hello,有,即说明加载成功
可以看到/dev/hello /sys/class/hello/hello/val /proc/hello/hello 文件已经生成
我们进入/sys/class/hello/hello目录下 输入:echo hah > val 看串口打印是否有输出
[230352.249030@2] hello_val_store.buf:haha
[230352.249030@2] count:5
[230352.249803@2] _hello_set_val.buf: haha
[230352.249803@2] count:5
[230352.256572@2] _hello_set_val.dev->val:123
[230352.256572@2] oworld count:5
[230352.263440@2] _hello_set_val.dev->val:haha
[230352.263440@2] world count:5
以上输出说明已经写入成功,使用命令 cat val 读取刚刚写入的字符串
[230510.445927@0] hello_val_show.
[230510.446117@0] haha
[230510.446117@0] world
看看haha打印出来说明写入是成功的
四,使用/dev/hello节点写简单程序调用测试
在驱动的同级目录下新建hellotest目录,在目录下新建jni目录,在jni目录下新建Android.mk Application.mk hellotest.c 三个文件
hellotest.c 这个文件是用来打开访问/dev/hello文件节点,读写字符串操作
/*#include<stdio.h>
int main(){
return 0;
}*/
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#define HELLO_DEVICE "/dev/hello"
int main(){
int fd = -1;
char * str = malloc(20);
fd = open(HELLO_DEVICE, O_RDWR);
if (fd == -1)
{
printf("Failed to open device %s.\n", HELLO_DEVICE);
return -1;
}
printf("Read original value:\n");
read(fd, str, 20);
printf("read data: %s\n", str);
strncpy(str, "nihao", sizeof("nihao"));
printf("write %s\n", str);
write(fd, str, sizeof(str));
printf("Read the value again:\n");
read(fd, str, 20);
printf("read data: %s\n", str);
close(fd);
return 0;
}
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CFLAGS := -Wall -Wextra -Werror -Wunused
LOCAL_SRC_FILES := \
hellotest.c
LOCAL_MODULE := hellotest
include $(BUILD_EXECUTABLE)
Application.mk
APP_ABI := armeabi
以上三个文件创建成功,在该路径下执行ndk-build -B ,编译可执行测试程序
[armeabi] Compile thumb : hellotest <= hellotest.c
[armeabi] Executable : hellotest
[armeabi] Install : hellotest => libs/armeabi/hellotest
将编译出来的libs/armeabi/hellotest可执行程序push到开发板内,
adb push hellotest /data
修改权限 adb shell ; chmod 777 /data/hellotest
执行./hellotest
输出
Read original value:
read data: hello
write nihao
Read the value again:
read data: nihao
开始读出hello字串,后面写入nihao ,读出nihao,可见我们测试驱动成功
感谢 阳光玻璃杯 https://blog.csdn.net/u011913612/article/details/52516303