Android 尝试写一份Linux 字符设备驱动

       从事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

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值