精华内容
下载资源
问答
  • 2020-04-16 22:29:41

    编写驱动程序的步骤


    1.确定主设备号
    2.定义file_operation结构体
    3.实现open,close,read,write等函数,填入file_operation结构体
    4.把file_operation结构体告诉内核:注册驱动程序
    5.谁来注册驱动程序,需要入口函数,安装驱动程序时就会调用这个入口函数
    6.对应的出口函数
    7.其他需要完善的,提供设备信息,自动创建设备节点

    更多相关内容
  • 尽管触摸屏正在迅速普及开来,但大多数开发...本文将介绍如何编写软件驱动程序,从而能够使用这些微处理器配置、校准触摸屏以及对触摸屏输入持续响应。最终将提供可免费下载和使用的工作代码,作为读者进一步设计的基础
  • 本文主要介绍在Ubuntu 上为Android系统编写Linux内核驱动程序,这里对编写驱动程序做了详细的说明,对研究Android源码和HAL都有巨大的帮助,有需要的小伙伴可以参考下
  • 首先,以“OS级别”运行的代码不需要与操作系统相同的语言编写。它只需要能够与OS代码链接在一起。几乎所有的语言都可以与C进行互操作,这真的是所有需要的。所以在语言方面,技术上没有问题。 Java函数可以调用C...

    有几种方法可以做到这一点。

    首先,以“OS级别”运行的代码不需要与操作系统相同的语言编写。它只需要能够与OS代码链接在一起。几乎所有的语言都可以与C进行互操作,这真的是所有需要的。

    所以在语言方面,技术上没有问题。 Java函数可以调用C函数,C函数可以调用Java函数。如果操作系统不是用C编写的(就是说,为了C语言编写的参数,OS C代码可以调用一些中间的C代码,它们转发到你的Java,反之亦然。 C几乎是编程的通用语言。

    一旦程序被编译(本地代码),它的源语言就不再相关了。无论编译之前编写源代码是哪种语言,汇编程序看起来都是一样的。只要您使用与操作系统相同的调用约定,这没有问题。

    更大的问题是运行时支持。 OS中没有很多软件服务可用。通常没有Java虚拟机,例如。 (没有理由在技术上不能,但通常,但通常,它是安全的,假设它不存在)。

    不幸的是,在其“默认”表示中,作为Java字节码,Java程序需要大量的基础设施。它需要Java VM来解释和JIT字节码,它需要类库等等。

    但有两种方法:

    >在内核中支持Java。这将是一个不寻常的一步,但可以做到。

    >或者将Java源代码编译成本机格式。 Java程序不必编译为Java字节码。您可以将其编译为x86汇编程序。同样适用于您使用的任何类库。那些也可以编译成汇编程序。当然,Java类库的一部分需要一些不可用的操作系统功能,但是可以避免使用这些类。

    所以是的,可以做到。但是这并不简单,你不知道你会获得什么。

    当然另一个问题可能是Java不会让你访问任意的内存位置,这会使很多硬件通信很棘手。但是也可以通过调用非常简单的C函数来简单地返回相关的内存区域作为Java的数组。

    展开全文
  • simulink硬件驱动
  • 摘要:介绍实时操作系统QNX4.25下编写设备驱动程序的大体框架、底层细节以及诸多注意点。针对使用较为普遍的PCI设备作为较为详细的描述。 关键词:驱动程序 QNX 实时操作系统 PCI引言QNX是一个多任务、多用户、...
  • 驱动程序 围绕CppPotpourri编写的无阻塞灵活硬件驱动程序的集合。
  • xPC硬件驱动编写

    2013-08-21 15:59:09
    文中介绍了Matlab环境下xPC硬件驱动编写指南,英文的。
  • 其主要设计目标之一就是要使应用程序和系统能独立于具体的计算机体系结构和硬件平台,表现在设备驱动程序设计上,对于已有的Linux标准设备驱动程序可以直接继续使用,只需为其增加应用层JNI接口。但对于Linux没有的...
  • 组态王驱动编写

    2018-04-25 16:47:25
    在组态王驱动编写过程中,最主要的工作是在开发包的模板下,对一些接口函数进行修改,以符合具体硬件的要求,以下列出了驱动函数向上向下发送数据时需要调用及被调用的关系,能够方便驱动编写
  • 未经测试,只是下载的,因为在本机无法测试,所以不收积分,有时间会找机器测试,经过查毒没有发现异常
  • 美国微软公司出品的Windows98以其友好的图形用户界面,在我国赢得了广泛的市场。在给广大办公环境工作人员...Windows98内核管理机制非常复杂,因而编写虚拟驱动程序也变得十分困难,要想编写虚拟驱动程序,就必须对Wind
  • 字符设备驱动编写流程,对设备初始化和释放; 2、把数据从内核传送到硬件和从硬件读取数据; 3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据; 4、检测和处理设备出现的错误
  • 简单来说,硬件驱动程序一方面分布在Linux内核中,另一方面分布在用户空间的硬件抽象层中。接着Ubuntu Android系统上编写Linux内核驱动程序实现方法一文中举例子说明了如何在Linux内核编写驱动程序。在这一篇文章中...
  • 基于模型设计—自动代码生成之硬件驱动,RTW 工具箱做自动代码生成时,硬件驱动编写方法
  • 设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:  1、对设备初始化...
  • 文章目录示例程序目标编写驱动程序创建驱动目录和驱动程序创建 Makefile 文件编译驱动模块加载驱动模块设备节点应用程序卸载驱动模块 别人的经验,我们的阶梯! 大家好,我是道哥。 在前几篇文章中,我们一块讨论了...

    作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++、嵌入式、Linux

    关注下方公众号,回复【书籍】,获取 Linux、嵌入式领域经典书籍;回复【PDF】,获取所有原创文章( PDF 格式)。


    别人的经验,我们的阶梯!

    大家好,我是道哥。

    在前几篇文章中,我们一块讨论了:在 Linux 系统中,编写字符设备驱动程序的基本框架,主要是从代码流程和 API 函数这两方面触发。

    这篇文章,我们就以此为基础,写一个有实际应用功能的驱动程序:

    1. 在驱动程序中,初始化 GPIO 设备,自动创建设备节点;

    2. 在应用程序中,打开 GPIO 设备,并发送控制指令设置 GPIO 口的状态;

    示例程序目标

    编写一个驱动程序模块:mygpio.ko

    当这个驱动模块被加载的时候,在系统中创建一个 mygpio 类设备,并且在 /dev 目录下,创建 4 个设备节点:

    /dev/mygpio0

    /dev/mygpio1

    /dev/mygpio2

    /dev/mygpio3

    因为我们现在是在 x86 平台上来模拟 GPIO 的控制操作,并没有实际的 GPIO 硬件设备。

    因此,在驱动代码中,与硬件相关部分的代码,使用宏 MYGPIO_HW_ENABLE 控制起来,并且在其中使用printk输出打印信息来体现硬件的操作。

    在应用程序中,可以分别打开以上这 4GPIO 设备,并且通过发送控制指令,来设置 GPIO 的状态。

    编写驱动程序

    以下所有操作的工作目录,都是与上一篇文章相同的,即:~/tmp/linux-4.15/drivers/

    创建驱动目录和驱动程序

    $ cd linux-4.15/drivers/
    $ mkdir mygpio_driver
    $ cd mygpio_driver
    $ touch mygpio.c
    

    mygpio.c 文件的内容如下(不需要手敲,文末有代码下载链接):

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/ctype.h>
    #include <linux/device.h>
    #include <linux/cdev.h>
    
    // GPIO 硬件相关宏定义
    #define MYGPIO_HW_ENABLE
    
    // 设备名称
    #define MYGPIO_NAME			"mygpio"
    
    // 一共有4个 GPIO 口
    #define MYGPIO_NUMBER		4
    
    // 设备类
    static struct class *gpio_class;
    
    // 用来保存设备
    struct cdev gpio_cdev[MYGPIO_NUMBER];
    
    // 用来保存设备号
    int gpio_major = 0;
    int gpio_minor = 0;
    
    #ifdef MYGPIO_HW_ENABLE
    // 硬件初始化函数,在驱动程序被加载的时候(gpio_driver_init)被调用
    static void gpio_hw_init(int gpio)
    {
    	printk("gpio_hw_init is called: %d. \n", gpio);
    }
    
    // 硬件释放
    static void gpio_hw_release(int gpio)
    {
    	printk("gpio_hw_release is called: %d. \n", gpio);
    }
    
    // 设置硬件GPIO的状态,在控制GPIO的时候(gpio_ioctl)被调研
    static void gpio_hw_set(unsigned long gpio_no, unsigned int val)
    {
    	printk("gpio_hw_set is called. gpio_no = %ld, val = %d. \n", gpio_no, val);
    }
    #endif
    
    // 当应用程序打开设备的时候被调用
    static int gpio_open(struct inode *inode, struct file *file)
    {
    	
    	printk("gpio_open is called. \n");
    	return 0;	
    }
    
    // 当应用程序控制GPIO的时候被调用
    static long gpio_ioctl(struct file* file, unsigned int val, unsigned long gpio_no)
    {
    	printk("gpio_ioctl is called. \n");
    	
    	// 检查设置的状态值是否合法
    	if (0 != val && 1 != val)
    	{
    		printk("val is NOT valid! \n");
    		return 0;
    	}
    
        // 检查设备范围是否合法
    	if (gpio_no >= MYGPIO_NUMBER)
    	{
    		printk("dev_no is invalid! \n");
    		return 0;
    	}
    
    	printk("set GPIO: %ld to %d. \n", gpio_no, val);
    
    #ifdef MYGPIO_HW_ENABLE
        // 操作 GPIO 硬件
    	gpio_hw_set(gpio_no, val);
    #endif
    
    	return 0;
    }
    
    static const struct file_operations gpio_ops={
    	.owner = THIS_MODULE,
    	.open  = gpio_open,
    	.unlocked_ioctl = gpio_ioctl
    };
    
    static int __init gpio_driver_init(void)
    {
    	int i, devno;
    	dev_t num_dev;
    
    	printk("gpio_driver_init is called. \n");
    
    	// 动态申请设备号(严谨点的话,应该检查函数返回值)
    	alloc_chrdev_region(&num_dev, gpio_minor, MYGPIO_NUMBER, MYGPIO_NAME);
    
    	// 获取主设备号
    	gpio_major = MAJOR(num_dev);
    	printk("gpio_major = %d. \n", gpio_major);
    
    	// 创建设备类
    	gpio_class = class_create(THIS_MODULE, MYGPIO_NAME);
    
    	// 创建设备节点
    	for (i = 0; i < MYGPIO_NUMBER; ++i)
    	{
    		// 设备号
    		devno = MKDEV(gpio_major, gpio_minor + i);
    		
    		// 初始化 cdev 结构
    		cdev_init(&gpio_cdev[i], &gpio_ops);
    
    		// 注册字符设备
    		cdev_add(&gpio_cdev[i], devno, 1);
    
    		// 创建设备节点
    		device_create(gpio_class, NULL, devno, NULL, MYGPIO_NAME"%d", i);
    	}
    
    #ifdef MYGPIO_HW_ENABLE
        // 初始化 GPIO 硬件
    	for (i = 0; i < MYGPIO_NUMBER; ++i)
    	{
    		gpio_hw_init(i);
    	}
    #endif
    
    	return 0;
    }
    
    static void __exit gpio_driver_exit(void)
    {
    	int i;
    	printk("gpio_driver_exit is called. \n");
    
    	// 删除设备和设备节点
    	for (i = 0; i < MYGPIO_NUMBER; ++i)
    	{
    		cdev_del(&gpio_cdev[i]);
    		device_destroy(gpio_class, MKDEV(gpio_major, gpio_minor + i));
    	}
    
    	// 释放设备类
    	class_destroy(gpio_class);
    
    #ifdef MYGPIO_HW_ENABLE
        // 释放 GPIO 硬件
    	for (i = 0; i < MYGPIO_NUMBER; ++i)
    	{
    		gpio_hw_release(i);
    	}
    #endif
    
    	// 注销设备号
    	unregister_chrdev_region(MKDEV(gpio_major, gpio_minor), MYGPIO_NUMBER);
    }
    
    MODULE_LICENSE("GPL");
    module_init(gpio_driver_init);
    module_exit(gpio_driver_exit);
    

    相对于前几篇文章来说,上面的代码稍微有一点点复杂,主要是多了宏定义 MYGPIO_HW_ENABLE 控制部分的代码。

    比如:在这个宏定义控制下的三个与硬件相关的函数:

    gpio_hw_init()

    gpio_hw_release()

    gpio_hw_set()

    就是与GPIO硬件的初始化、释放、状态设置相关的操作。

    代码中的注释已经比较完善了,结合前几篇文章中的函数说明,还是比较容易理解的。

    从代码中可以看出:驱动程序使用 alloc_chrdev_region 函数,来动态注册设备号,并且利用了 Linux 应用层中的 udev 服务,自动在 /dev 目录下创建了设备节点

    另外还有一点:在上面示例代码中,对设备的操作函数只实现了 open 和 ioctl 这两个函数,这是根据实际的使用场景来决定的。

    这个示例中,只演示了如何控制 GPIO 的状态

    你也可以稍微补充一下,增加一个read函数,来读取某个GPIO口的状态。

    控制 GPIO 设备,使用 write 或者 ioctl 函数都可以达到目的,只是 ioctl 更灵活一些。

    创建 Makefile 文件

    $ touch Makefile
    

    内容如下:

    ifneq ($(KERNELRELEASE),)
    	obj-m := mygpio.o
    else
    	KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    	PWD := $(shell pwd)
    default:
    	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    clean:
    	$(MAKE) -C $(KERNEL_PATH) M=$(PWD) clean
    endif
    

    编译驱动模块

    $ make
    

    得到驱动程序: mygpio.ko

    加载驱动模块

    在加载驱动模块之前,先来检查一下系统中,几个与驱动设备相关的地方。

    先看一下 /dev 目录下,目前还没有设备节点( /dev/mygpio[0-3] )。

    $ ls -l /dev/mygpio*
    ls: cannot access '/dev/mygpio*': No such file or directory
    

    再来查看一下 /proc/devices 目录下,也没有 mygpio 设备的设备号。

    $ cat /proc/devices
    

    为了方便查看打印信息,把dmesg输出信息清理一下:

    $ sudo dmesg -c
    

    现在来加载驱动模块,执行如下指令:

    $ sudo insmod mygpio.ko
    

    当驱动程序被加载的时候,通过 module_init( ) 注册的函数 gpio_driver_init() 将会被执行,那么其中的打印信息就会输出。

    还是通过 dmesg 指令来查看驱动模块的打印信息:

    $ dmesg 
    

    可以看到:操作系统为这个设备分配的主设备号是 244,并且也打印了GPIO硬件的初始化函数的调用信息。

    此时,驱动模块已经被加载了!

    来查看一下 /proc/devices 目录下显示的设备号:

    $ cat /proc/devices
    

    设备已经注册了,主设备号是: 244

    设备节点

    由于在驱动程序的初始化函数中,使用 cdev_adddevice_create 这两个函数,自动创建设备节点。

    所以,此时我们在 /dev 目录下,就可以看到下面这4个设备节点:

    现在,设备的驱动程序已经加载了,设备节点也被创建好了,应用程序就可以来控制 GPIO 硬件设备了。

    应用程序

    应用程序仍然放在 ~/tmp/App/ 目录下。

    $ mkdir ~/tmp/App/app_mygpio
    $ cd ~/tmp/App/app_mygpio
    $ touch app_mygpio.c
    

    文件内容如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <assert.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    
    #define MY_GPIO_NUMBER		4
    
    // 4个设备节点
    char gpio_name[MY_GPIO_NUMBER][16] = {
    	"/dev/mygpio0",
    	"/dev/mygpio1",
    	"/dev/mygpio2",
    	"/dev/mygpio3"
    };
    
    
    int main(int argc, char *argv[])
    {
    	int fd, gpio_no, val;
    
        // 参数个数检查
    	if (3 != argc)
    	{
    		printf("Usage: ./app_gpio gpio_no value \n");
    		return -1;
    	}
    
    	gpio_no = atoi(argv[1]);
    	val = atoi(argv[2]);
    
        // 参数合法性检查
    	assert(gpio_no < MY_GPIO_NUMBER);
    	assert(0 == val || 1 == val);
    
    	// 打开 GPIO 设备
    	if((fd = open(gpio_name[gpio_no], O_RDWR | O_NDELAY)) < 0){
    		printf("%s: open failed! \n", gpio_name[gpio_no]);
    		return -1;
    	}
    
    	printf("%s: open success! \n", gpio_name[gpio_no]);
    
    	// 控制 GPIO 设备状态
    	ioctl(fd, val, gpio_no);
    	
    	// 关闭设备
    	close(fd);
    }
    

    以上代码也不需要过多解释,只要注意参数的顺序即可。

    接下来就是编译和测试了:

    $ gcc app_mygpio.c -o app_mygpio
    

    执行应用程序的时候,需要携带2个参数:GPIO 设备编号(0 ~ 3),设置的状态值(0 或者 1)

    这里设置一下/dev/mygpio0这个设备,状态设置为1

    $ sudo ./app_mygpio 0 1
    [sudo] password for xxx: <输入用户密码>
    /dev/mygpio0: open success!
    

    如何确认/dev/mygpio0这个GPIO的状态确实被设置为1了呢?当然是看 dmesg 指令的打印信息:

    $ dmesg
    

    通过打印信息可以看到:确实执行了【设置 mygpio0 的状态为 1】的动作。

    再继续测试一下:设置 mygpio0 的状态为 0:

    $ sudo ./app_mygpio 0 0
    

    当然了,设置其他几个GPIO口的状态,都是可以正确执行的!

    卸载驱动模块

    卸载指令:

    $ sudo rmmod mygpio
    

    此时,/proc/devices 下主设备号 244mygpio 已经不存在了。

    再来看一下 dmesg的打印信息:

    可以看到:驱动程序中的 gpio_driver_exit( ) 被调用执行了。

    并且,/dev 目录下的 4 个设备节点,也被函数 device_destroy() 自动删除了!


    ------ End ------

    文中的测试代码,已经放在网盘了。

    在公众号【IOT物联网小镇】后台回复关键字:1128,即可获取下载地址。

    谢谢!

    推荐阅读

    【1】《Linux 从头学》系列文章

    【2】C语言指针-从底层原理到花式技巧,用图文和代码帮你讲解透彻

    【3】原来gdb的底层调试原理这么简单

    【4】内联汇编很可怕吗?看完这篇文章,终结它!

    其他系列专辑:精选文章应用程序设计物联网C语言

    展开全文
  • OS上的硬件驱动;Linux驱动程序;课程安排;Linux内核模块;Linux内核模块;Linux内核模块;Linux内核模块;Linux内核模块;Linux内核模块;内核模块交叉开发;内核模块交叉开发;内核模块交叉开发;内核模块交叉开发;内核模块...
  • 蔡工驱动开发实战之编写I2C驱动和Debug过程
  • 美国微软公司出品的Windows98以其友好的图形用户界面,在我国赢得了广泛的市场。在给广大办公环境工作人员...Windows98内核管理机制非常复杂,因而编写虚拟驱动程序也变得十分困难,要想编写虚拟驱动程序,就必须对Wind
  • 我们知道,Android系统的应用程序是用Java语言编写的,而硬件驱动程序是用C语言来实现的,那么,Java接口如何去访问C接口呢?众所周知,Java提供了JNI方法调用,同样,在Android系统中,Java应用程序通过JNI来调用...
  • 本文通过一个简单的MCF5307定时器驱动程序设计,表述了隐藏硬件的思想以及用于嵌入式驱动程序的编写的方法和步骤,可以推广到其他嵌入式系统驱动程序设计中。
  • 硬件的使用与驱动开发入门,详细解释驱动工作原理
  • 在Ubuntu上为Android系统编写Linux内核驱动程序

    万次阅读 多人点赞 2011-06-26 12:10:00
    目前,对Android人才需求一类是偏向硬件驱动的Android人才需求,一类是偏向软件应用的Android人才需求。总的来说,对有志于从事Android硬件驱动的开发工程师来说,现在是一个大展拳脚的机会。那么,就让我们一起来看...

            在智能手机时代,每个品牌的手机都有自己的个性特点。正是依靠这种与众不同的个性来吸引用户,营造品牌凝聚力和用户忠城度,典型的代表非iphone莫属了。据统计,截止2011年5月,AppStore的应用软件数量达381062个,位居第一,而Android Market的应用软件数量达294738,紧随AppStore后面,并有望在8月份越过AppStore。随着Android系统逐步扩大市场占有率,终端设备的多样性亟需更多的移动开发人员的参与。据业内统计,Android研发人才缺口至少30万。目前,对Android人才需求一类是偏向硬件驱动的Android人才需求,一类是偏向软件应用的Android人才需求。总的来说,对有志于从事Android硬件驱动的开发工程师来说,现在是一个大展拳脚的机会。那么,就让我们一起来看看如何为Android系统编写内核驱动程序吧。

    《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

            这里,我们不会为真实的硬件设备编写内核驱动程序。为了方便描述为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目录:

           USER-NAME@MACHINE-NAME:~/Android$ cd kernel/common/drivers

           USER-NAME@MACHINE-NAME:~/Android/kernel/common/drivers$ mkdir hello

           三. 在hello目录中增加hello.h文件:

    #ifndef _HELLO_ANDROID_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_PROC_NAME  "hello"
    #define HELLO_DEVICE_CLASS_NAME "hello"
    
    struct hello_android_dev {
    	int val;
    	struct semaphore sem;
    	struct cdev dev;
    };
    
    #endif

       这个头文件定义了一些字符串常量宏,在后面我们要用到。此外,还定义了一个字符设备结构体hello_android_dev,这个就是我们虚拟的硬件设备了,val成员变量就代表设备里面的寄存器,它的类型为int,sem成员变量是一个信号量,是用同步访问寄存器val的,dev成员变量是一个内嵌的字符设备,这个Linux驱动程序自定义字符设备结构体的标准方法。

       四.在hello目录中增加hello.c文件,这是驱动程序的实现部分。驱动程序的功能主要是向上层提供访问设备的寄存器的值,包括读和写。这里,提供了三种访问设备寄存器的方法,一是通过proc文件系统来访问,二是通过传统的设备文件的方法来访问,三是通过devfs文件系统来访问。下面分段描述该驱动程序的实现。

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

    #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 "hello.h"
    
    /*主设备和从设备号变量*/
    static int hello_major = 0;
    static int hello_minor = 0;
    
    /*设备类别和设备变量*/
    static struct class* hello_class = NULL;
    static struct hello_android_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 struct file_operations hello_fops = {
    	.owner = THIS_MODULE,
    	.open = hello_open,
    	.release = hello_release,
    	.read = hello_read,
    	.write = hello_write, 
    };
    
    /*访问设置属性方法*/
    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);

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

    /*打开设备方法*/
    static int hello_open(struct inode* inode, struct file* filp) {
    	struct hello_android_dev* dev;        
    	
    	/*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/
    	dev = container_of(inode->i_cdev, struct hello_android_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_android_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_android_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;
    }

            定义通过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_android_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);
    }
    
    /*把缓冲区buf的值写到设备寄存器val中去,内部使用*/
    static ssize_t __hello_set_val(struct hello_android_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;
    }
    
    /*读取设备属性val*/
    static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf) {
    	struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);        
    
    	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_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);  
    	
    	return __hello_set_val(hdev, buf, count);
    }

            定义通过proc文件系统访问方法,主要实现了hello_proc_read和hello_proc_write两个方法,同时定义了在proc文件系统创建和删除文件的方法hello_create_proc和hello_remove_proc:

    /*读取设备寄存器val的值,保存在page缓冲区中*/
    static ssize_t hello_proc_read(char* page, char** start, off_t off, int count, int* eof, void* data) {
    	if(off > 0) {
    		*eof = 1;
    		return 0;
    	}
    
    	return __hello_get_val(hello_dev, page);
    }
    
    /*把缓冲区的值buff保存到设备寄存器val中去*/
    static ssize_t hello_proc_write(struct file* filp, 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 = __hello_set_val(hello_dev, page, len);
    
    out:
    	free_page((unsigned long)page);
    	return err;
    }
    
    /*创建/proc/hello文件*/
    static void hello_create_proc(void) {
    	struct proc_dir_entry* entry;
    	
    	entry = create_proc_entry(HELLO_DEVICE_PROC_NAME, 0, NULL);
    	if(entry) {
    		entry->owner = THIS_MODULE;
    		entry->read_proc = hello_proc_read;
    		entry->write_proc = hello_proc_write;
    	}
    }
    
    /*删除/proc/hello文件*/
    static void hello_remove_proc(void) {
    	remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL);
    }

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

    /*初始化设备*/
    static int  __hello_setup_dev(struct hello_android_dev* dev) {
    	int err;
    	dev_t devno = MKDEV(hello_major, hello_minor);
    
    	memset(dev, 0, sizeof(struct hello_android_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;
    	}        
    
    	/*初始化信号量和寄存器val的值*/
    	init_MUTEX(&(dev->sem));
    	dev->val = 0;
    
    	return 0;
    }
    
    /*模块加载方法*/
    static int __init hello_init(void){ 
    	int err = -1;
    	dev_t dev = 0;
    	struct device* temp = NULL;
    
    	printk(KERN_ALERT"Initializing hello device.\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);        
    
    	/*分配helo设备结构体变量*/
    	hello_dev = kmalloc(sizeof(struct hello_android_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/class/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.");
    		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.");                
    		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"Destroy hello device.\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);
    	}        
    
    	/*释放设备号*/
    	unregister_chrdev_region(devno, 1);
    }
    
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("First Android Driver");
    
    module_init(hello_init);
    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/
            八. 配置编译选项:
             USER-NAME@MACHINE-NAME:~/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选项。
            九. 编译:
             USER-NAME@MACHINE-NAME:~/Android/kernel/common$ make
            编译成功后,就可以在hello目录下看到hello.o文件了,这时候编译出来的zImage已经包含了hello驱动。
            十. 参照 在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)一文所示,运行新编译的内核文件,验证hello驱动程序是否已经正常安装:
             USER-NAME@MACHINE-NAME:~/Android$ emulator -kernel ./kernel/common/arch/arm/boot/zImage &
            USER-NAME@MACHINE-NAME:~/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驱动程序交互,敬请期待。
    老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
    展开全文
  • 一、在Android内核源代码工程(kernel)中编写硬件驱动程序.............................4 二、在Android系统中增加C可执行程序来访问硬件驱动程序。.....................14 三、在Android硬件抽象层(HAL)增加接口...
  • 源代码通过获取q驱动inf文件进行列表所有计算机驱动程序信息,并可以按照备份到文件夹、压缩备份、自解压格式备份等方式备份硬件驱动程序,如果按照自解压备份的话,可以通过程序自带的驱动自动安装助手进行自动还原...
  • 驱动程序可以是针对某一特定硬件的,为系统提供管理硬件的各种功能;也可以是针对系统设备的,对系统的输入输出做一些处理,实现特定的功能,比如当软件要做的事用应用程序无法实现或者难以实现某种功能时,但驱动...
  • Ettus Research USRP™ 系列软件定义无线电 (SDR) 是多功能设备,允许用户在通用硬件平台上以各种频率和设置发送和接收许多不同的自定义波形。 商业、学术和军事客户采用灵活且可重复使用的 USRP 硬件进行研究、...
  • 本文详细地介绍如何Linux系统的硬件驱动程序的编写原理,指出哪些内核例程将会被调用、如何初始化驱动程序及如何分配内存等等。
  • LED驱动代码编写

    千次阅读 2021-12-13 17:42:58
    为了编写LED驱动程序,需要查看开发板的原理图,查看需要配置的引脚。这里LED驱动代码的编写基于100ask-imx6ull mini开发板。 一、查看原理图 由LED硬件图可以得知配置GPIO5_3 输出低电平,LED点亮,GPIO5_3输出高...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 130,755
精华内容 52,302
关键字:

编写硬件驱动