从事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/目录下创建设备类别目录hellohello_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 目录下分别创建设备文件hellotemp = 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 目录下创建属性文件valerr = 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 HELLOtristate "Test Driver"default nhelpThis 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_OBJobj-$(CONFIG_HELLO)+= hello.o
export CONFIG_HELLO = mall:modulesmodules:$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KSRC) M=$(shell pwd) modules.PHONY: modules cleanclean:$(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

Android 尝试写一份Linux 字符设备驱动相关推荐

  1. linux字符设备驱动在哪里设置,从点一个灯开始学写Linux字符设备驱动!

    原标题:从点一个灯开始学写Linux字符设备驱动! [导读] 前一篇文章,介绍了如何将一个hello word模块编译进内核或者编译为动态加载内核模块,本篇来介绍一下如何利用Linux驱动模型来完成一 ...

  2. linux设备模型 字符设备,Linux 字符设备驱动模型之框架解说

    一.软件操作硬件设备模型 在进行嵌入式开发的过程中,在常做的事情就是驱动配置硬件设 备,然后根据功能需求使用硬件设备,实现功能的逻辑.如下图为其 相互之间的关系. 如上图所示: 驱动程序:主要作为操作 ...

  3. Linux 字符设备驱动结构(四)—— file_operations 结构体知识解析

    前面在 Linux 字符设备驱动开发基础 (三)-- 字符设备驱动结构(中) ,我们已经介绍了两种重要的数据结构 struct inode{...}与 struct file{...} ,下面来介绍另 ...

  4. Linux字符设备驱动

    /*Linux字符设备驱动源代码scdd.c*/ #include <linux/init.h>   /*模块头文件*/ #include <linux/module.h> # ...

  5. Linux 字符设备驱动的编写

    Linux 字符设备驱动的编写 作者:解琛 时间:2020 年 8 月 17 日 Linux 字符设备驱动的编写 一.Linux 设备分类 二.open() 三.数据结构 3.1 struct fil ...

  6. Linux 字符设备驱动结构(三)—— file、inode结构体及chardevs数组等相关知识解析

    前面我们学习了字符设备结构体cdev Linux 字符设备驱动开发 (一)-- 字符设备驱动结构(上)  下面继续学习字符设备另外几个重要的数据结构. 先看下面这张图,这是Linux 中虚拟文件系统. ...

  7. ()shi linux字符设备,Linux字符设备驱动基础(三)

    Linux字符设备驱动基础(三) 6 创建设备节点 6.1 手动创建设备节点 查看申请的设备名及主设备号: cat /proc/devices # cat /proc/devices Characte ...

  8. linux字符设备文件的打开操作,Linux字符设备驱动模型之字符设备初始化

    因为Linux字符设备驱动主要依赖于struct cdev结构,原型为: 所以我们需要对所使用到的结构成员进行配置,驱动开发所使用到的结构成员分别为:[unsigned int count;].[de ...

  9. linux生成驱动编译的头文件,嵌入式Linux字符设备驱动——5生成字符设备节点

    嵌入式Linux字符设备驱动开发流程--以LED为例 前言 留空 头文件 #include 查看系统设备类 ls /sys/class 设备类结构体 文件(路径):include/linux/devi ...

最新文章

  1. 这次终于不再为 iptables 犯迷糊了!
  2. ubuntu18 安装 chrome
  3. JavaScript校验身份证,包含省份、长度、出生年月日、校验位的检测、性别、年龄...
  4. 斐波那契数列的3种求法及几种素数筛法
  5. 对于Eclipse的正确用法
  6. 【DFS】HDU 1364 POJ 1071 Illusive Chase
  7. JavaScript-严格检查模式
  8. python中easygui有几种_一、Python 模块EasyGui详细介绍
  9. 子类重写方法aop切不到_Spring-aop 全面解析(从应用到原理)
  10. java 僵尸进程_有关僵尸进程和孤儿进程的解释
  11. 查看tomcat版本信息
  12. 分析Redis集群原理
  13. Android识别图片中的颜色
  14. java 调用 pb dll_[转载]一个java调用delphi写的dll问题,郁闷了一天一晚解决
  15. 花生壳 内网穿透配置
  16. 360度全景视频后视镜
  17. css 侧栏跟随_JS+CSS实现侧边栏跟随浏览器滚动效果
  18. 超好看的樱花飘落网络科技官网源码
  19. SWOOLE进阶-06网络IO模型-阻塞模型
  20. iStat Menus for mac中文

热门文章

  1. TCP长连接和短连接的区别
  2. 全球地表温度月值数据集(1980-2022) NOAAGlobalTemp
  3. acs cisco 查看log_ACS常用操作(实战)
  4. mysql如何查询是否大小写敏感_MySQL查询大小写是否敏感问题分析
  5. 推荐一波有趣且实用的奥特曼表情包
  6. InnoDB MVCC 机制
  7. 利用vue-cli(脚手架)一步一步构建一个仿当当网项目
  8. 都2022年了,还有人在吵硬盘分区这事。。。
  9. 计算机屏幕变红色,win10系统电脑屏幕变成红色红屏怎么解决
  10. WPF XAML概览