裸机开发之驱动开发

这篇具有很好参考价值的文章主要介绍了裸机开发之驱动开发。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、驱动开发的基础理解   

        在计算中,设备驱动程序是一种计算机程序,用于操作或控制连接到计算机的特定类型的设备。驱动程序提供了与硬件设备的软件接口,使操作系统和其他计算机程序可以访问硬件功能,而无需了解有关所使用硬件的精确细节。

驱动程序通过硬件连接到的计算机总线或通信子系统与设备进行通信。当调用程序调用驱动程序中的例程时,驱动程序向设备发出命令。设备将数据发送回驱动程序后,驱动程序可以调用原始调用程序中的例程。驱动程序依赖于硬件且特定于操作系统。它们通常为那些有必要的时间异步的硬件接口提供终端处理。

        驱动开发就是在操作系统的基础上实现驱动程序。也可以理解为驱动程序就是中间件,让应用层去控制底层寄存器进行工作。

        其实编写的驱动文件也是.c为结尾的文件,但是其在编译阶段是调用的内核来进行编译,所生成的也是满足该内核的驱动文件.ko结尾文件,所以在内核使用的时候也就是没问题的。

1、驱动框架

一个基本的驱动框架如下所示:        4部分组成

1、头文件
        #include <linux/init.h>
        #include <linux/module.h>

2、驱动入口函数的声明,在内核加载驱动时,执行哪个函数;在内核卸载驱动时,执行哪个函数
        module_init(hello_init);           //声明:加载时的入口声明
        module_exit(hello_exit);         //声明:卸载时的入口声明

3、加载函数、卸载函数的实现
        //加载函数的实现:当内核加载驱动(内核执行这个驱动时,就会调用的函数)
        static int __init hello_init(void)
        {

            return 0;
        }

        //卸载函数的实现:当内核卸载驱动(内核删除这个驱动时,就会调用的函数)
        static void __exit hello_exit(void)
        {

        }

4、协议选择GPL
        MODULE_LICENSE("GPL");

例如下面一个LED灯的示例程序

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>

unsigned int * gpx1con;
unsigned int * gpx1dat;

//驱动与应用程序函数关联
int led_open (struct inode * inode, struct file * file)
{
	printk("led_open\n");
	return 0;
}

int led_close (struct inode * inode, struct file * file)
{
	printk("led_close\n");
	return 0;
}

ssize_t led_read (struct file * file, char __user * data, size_t size, loff_t * ops)
{
	//读取数据
	printk("led_read");
	copy_to_user(data,"nihao",6);
	return 0;
}
ssize_t led_write (struct file * file, const char __user * data, size_t size, loff_t * ops)
{
	int num = 0;
	copy_from_user(&num,data,size);
	printk("num = %d\n",num);
	if(num == 1){	
		*gpx1dat |= 1;
	}
	else{
		*gpx1dat &= ~1;
	}
	return 0;
}

const struct file_operations fops = {
	.open = led_open,//当应用程序调用open时,驱动则执行结构体成员open对应的赋值函数
	.release = led_close,
	.read = led_read,
	.write = led_write,
};
//入口实现
//加载 insmod
static int __init led_init(void)
{
	//字符设备框架
	//1、申请设备号,驱动必须有一个设备号来区别其他驱动
	int ret = -1;
	ret = register_chrdev(250,"led",&fops);
	if(ret == 0){
		printk("register ok\n");
	}
	//2、创建设备节点---设备文件
	//创建设备节点信息结构体
	struct class * cls_led = class_create(THIS_MODULE,"led cls");
	//创建设备文件
	dev_t devt = 250<<20 | 0;//设备号
	struct device * led_dev = device_create(cls_led,NULL,devt,NULL,"led%d",3);
	if(led_dev != NULL){
		printk("device create ok\n");
	}
	//初始化硬件
	//地址映射
	gpx1con = ioremap(0x11000c20,4);//指针变量就是映射寄存器地址
	*gpx1con = *gpx1con & ~(0xf) | 0x1;
	gpx1dat = ioremap(0x11000c24,4);
	*gpx1dat |= 1;
	return 0;
}
//卸载
static void __exit led_exit(void)
{
	与初始化逆序过程进行卸载
	//1、映射释放(中断释放)
	iounmap(映射的虚拟内存地址);----释放映射地址
	//2、释放设备文件
	void device_destroy(struct class * class,dev_t devt);
	//3、释放设备文件结构体
	void class_destroy(struct class * cls)
    //4、释放设备号
	void unregister_chrdev(unsigned int major,const char * name)
}
//入口声明
module_init(led_init);
module_exit(led_exit);

//GPL声明
MODULE_LICENSE("GPL");
#include <sys/types.h>//进行应用的例程实现点灯
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
	int a = 1;
	int fd = open("/dev/led3",O_RDWR);
	while(1)
	{
	    a = 1;
	    write(fd,&a,4);
	    sleep(1);
	    a = 0;
	    write(fd,&a,4);
	    sleep(1);
	}
	close(fd);
	return 0;
}

虽然有很多知识没学过,后面再进行介绍,这个时候看着或许有一些是不懂的

        驱动程序也就是让其通过文件方式进行控制底层的硬件寄存器,在驱动中实现文件io接口功能(与应用关联),应用程序调用文件io时,驱动程序也调用对应的文件io接口函数
    在结构体 struct file_operations 每一个成员变量都代表绑定一个系统调用(文件io)函数,只要对结构体中的成员赋值,就代表值绑定上一个文件io函数  

  struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        int (*iterate) (struct file *, struct dir_context *);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
        int (*show_fdinfo)(struct seq_file *m, struct file *f);
    };//函数指针的集合,

        每个函数指针赋值为函数地址,就代表当应用程序调用对应的文件io函数时,驱动就执行函数指针赋值的对应函数,例如下面的LED驱动例程中的

const struct file_operations fops = {
	.open = led_open,//当应用程序调用open时,驱动则执行结构体成员open对应的赋值函数
	.release = led_close,
	.read = led_read,
	.write = led_write,
};

        当然,这些对应的函数则需要我们自己去按照格式进行操作,如open对应的led_open就是一个函数,看上面的例程就可以看到对应的操作。

在上例中的函数实现是将外面传输的数据是拷贝到另外一个地方的,保证不改变数据的内容

copy_to_user()
copy_from_user()

既将数据进行copy到那里去

        在基础的驱动框架中,有一个加载和卸载函数,这个函数是安装或者卸载这个驱动的将会去处理的函数。也就是这个里面写的这两个,一个对应初始化,而另外一个则对于着卸载的时候将会触发的。里面的led_init才是执行函数。

module_init(led_init);        //对应初始化
module_exit(led_exit);      //对应卸载

        最后还需要加入一个协议(通用公共许可协议GPL)才能进行在最后内核编译时通过,不然将发生内核污染的编译错误

MODULE_LICENSE("GPL");        //GPL声明

2、驱动开发之字符设备驱动模型

I/O设备大致可以分为两类:块设备(block device)字符设备(character device)

        字符设备发送或接收的是字符流,而不考虑任何块结构。字符设备无法编址,也不存在任何寻址操作打印机、网络接口、鼠标(用做指点设备),大多数与磁盘不同的设备均可被视为字符设备。

        块设备将信息存储在固定大小的块中,每个块都有自己的地址。数据块的大小通常在512字节到32768字节之间。块设备的基本特征是每个块都能独立与其他块而读写。磁盘是最常见的块设备。

我总结的是:只要不是存储在固定大小的地址中的设备都为字符设备。

   1、必须要有一个设备号,用于在内核中的众多设备驱动进行区分

    2、必须要有一个设备文件,用户必须知道设备驱动对应的设备节点(设备文件)

    3、驱动对设备的操作,与应用程序中的系统调用关联,其实就是文件操作

上面在led_init()函数里面就有了符号设备的操作了

//字符设备框架
	//1、申请设备号,驱动必须有一个设备号来区别其他驱动
	int ret = -1;
	ret = register_chrdev(250,"led",&fops);
	if(ret == 0){
		printk("register ok\n");
	}
	//2、创建设备节点---设备文件
	//创建设备节点信息结构体
	struct class * cls_led = class_create(THIS_MODULE,"led cls");
	//创建设备文件
	dev_t devt = 250<<20 | 0;//设备号
	struct device * led_dev = device_create(cls_led,NULL,devt,NULL,"led%d",3);
	if(led_dev != NULL){
		printk("device create ok\n");
	}

其中使用到了一些不认识的函数,如下

register_chrdev()        //用于创建一个设备号

int register_chrdev(unsigned int major,const char *name,const struct file_operations *fops)
参数1:unsigned int major:主设备号,次设备号自动分配
			设备号:32bit = 主设备号(12bit) + 次设备号(20bit)
			主设备号:表示同一类设备;次设备号:表示同一类设备中的不同设备
参数2:const char *name:描述一个设备驱动信息,自定义
参数3:const struct file_operations *fops:文件操作对象,函数关联(使用结构体来存储驱动与应用程序的关联)
		

class_create()             //创建设备节点信息结构体

struct class * class_create(owner,name);
参数1:owner:拥有者,一般THIS_MODULE
参数2:name:字符串,描述信息
返回值:struct class *  信息结构体

device_create()      //创建设备文件

struct device *device_create(	struct class *class,struct device *parent,dev_t devt,void *drvdata, const char *fmt, ...)
参数1:struct class *class:class结构体,创建的设备文件的信息内容。通过 class_create()函数创建
参数2:struct device *parent:表示父类对象,一般直接写NULL
参数3:dev_t devt:设备号
参数4:void *drvdata:私有数据,一般填NULL
参数5:const char *fmt, ...:设备文件名
返回值:struct device *---------设备节点对象(设备文件描述)
			成功返回地址,失败返回NULL

        驱动控制硬件,控制外设,其实就是控制地址,通过地址往寄存器写入、读出控制内核驱动是通过虚拟地址操作,则就需要用到另外的函数,地址映射函数  ioremap

void * ioremap(cookie,size);
参数1:cookie:物理地址
参数2:size:映射内容大小,字节
返回值:返回映射成功后的虚拟内存地址,操作虚拟内存地址中的内容就是操作对应的物理地址空间内容

示例就如下:

	//地址映射和硬件初始化
	gpx1con = ioremap(0x11000c20,4);//指针变量就是映射寄存器地址
	*gpx1con = *gpx1con & ~(0xf) | 0x1;
	gpx1dat = ioremap(0x11000c24,4);
	*gpx1dat |= 1;

到这里就已经将上面示例所用到的所有实例已经解释完了,看到这里再去看例程就可以看懂了。

注意:在卸载驱动的函数里,再写的时候要用创建时候的倒序进行释放。如下:

static void __exit led_exit(void)
{
	与初始化逆序过程进行卸载
	//1、映射释放(中断释放)
	iounmap(映射的虚拟内存地址);----释放映射地址
	//2、释放设备文件
	void device_destroy(struct class * class,dev_t devt);
	//3、释放设备文件结构体
	void class_destroy(struct class * cls)
    //4、释放设备号
	void unregister_chrdev(unsigned int major,const char * name)
}

二、文件IO模型

http://t.csdn.cn/e5u8z     这就是如何实现中断的文章,可以去了解哈,因为后面的文件IO模型就是根据中断来进行演示的。

文件io模型一共分为四种

1、阻塞        2、非阻塞        3、IO多路复用        4、异步信号

1、阻塞io模型------休眠等待

阻塞:当进程读取外部资源(数据),但是外部资源没有准备好,进程就进行休眠等待资源可用

在使用这个时候必须要知道那些能阻塞那些不能阻塞不然容易出现问题的。

在应用中:read、wirte、accept、scanf 默认都是阻塞的

那么如何在驱动中实现阻塞:

1.1 创建一个等待队列的头,用于判断是否接收到数据的

wait_queue_head_t head;
init_waitqueue_head(&head);

1.2 在需要等待的位置(没有数据),就阻塞等待

wait_event_interruptible(wq,condition)-----根据参数是否进行阻塞等待,完成阻塞等待
参数1:wq:等待队列头,把当前进程加入到哪个等待队列中
参数2:condition:是否执行阻塞等待的条件
        condition:真---不进行阻塞
        condition:假---进行阻塞

1.3 2、合适位置进行阻塞唤醒

wake_up_interruptible(&head);

实现例程:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/sched.h>

struct key_node
{
	unsigned int major;
	struct class * cls;
	struct device * dev;
	dev_t devno;
	wait_queue_head_t head;
};
char data;//获取硬件数据存储的变量
int condition;
struct key_node key;

ssize_t key_read (struct file * file, char __user * buf, size_t size, loff_t * ops)
{
	wait_event_interruptible(key.head,condition);//阻塞等待
	int ret;//拷贝数据
	printk("key read\n");
	ret = copy_to_user(buf,&data,1);
	condition = 0;//下一次能够阻塞
	return 0;
}
int key_open (struct inode * inode, struct file * file)
{
	printk("key open\n");
	return 0;
}
int key_release (struct inode * inode , struct file * file)
{
	printk("key close\n");
	return 0;
}
irqreturn_t key_irq_handler(int irqno, void * dev)//实现中断处理函数
{
	//获取到数据
	data = 'q';
	condition = 1;
	printk("irqno is %d\n",irqno);
	printk("input char '%c'\n",data);//可以认为是一个字符‘q’,也可以从数据寄存器中获取真实的值
	//唤醒,把进程从等待队列中拿出来继续执行
	wake_up_interruptible(&(key.head));
	return IRQ_HANDLED;
}
const struct file_operations fops = {
	.open = key_open,
	.release = key_release,
	.read = key_read,

};
static int __init key_drv_init(void)
{
	//1、申请设备号
	key.major = 230;
	register_chrdev(key.major,"key drv",&fops);
	//2、创建设备节点
	key.cls = class_create(THIS_MODULE,"cls");
	key.devno = MKDEV(key.major,0);
	key.dev = device_create(key.cls,NULL,key.devno,NULL,"key3");
	//4、硬件初始化
	//a、获取中断号
	struct device_node * node = of_find_node_by_path("/key3_node");
	int irqno = irq_of_parse_and_map(node,0);
	//b、申请中断
	request_irq(irqno,key_irq_handler,IRQF_TRIGGER_FALLING,"key interrupt",NULL);
	//irqreturn_t (*handler)(int, void *)函数指着
	//handler = key_irq_handler
	//创建等待队列头
	init_waitqueue_head(&(key.head));
	return 0;
}
static void __exit key_exit(void)
{}
module_init(key_drv_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");

如果按键没有按下,则一直卡在那个地方。知道按键按下(中断发生)

2、非阻塞--------快速通过

非阻塞:在进行读写操作时,如果没有数据,就立即返回,如果有数据读取数据然后立即返回

和上面所用到的函数都是一样的,只是差距在判断condition是否为真,为真就不阻塞。

3、IO多路复用

4、异步信号

        异步通知(异步信号):当有数据的时候,驱动就会发送信号(SIGIO)给应用,应用就可以异步接收数据,而不用主动接收(可以去完成其他工作)。

        异步信号需要 分为两步,一步是触发这个异步信号,另一个则是收到这个异步信号进行相应的处理操作,一般触发信号是在驱动中去完成什么去触发这个信号,而处理操作则是在应用程序里进行处理。

应用程序的流程(处理信号):

signal(SIGIO,catch_signal);        //1、设置信号处理方式,catch_signal是处理函数

fcntl(fd,F_SETOWN,getpid());     //2、设置当前进程为SIGIO信号的属主进程  
//3、将io模式设置为异步模式        ----异步信号设置
        int flags = fcntl(fd,F_GETFL);
        flags |= FASYNC;//添加异步属性
        fcntl(fd,F_SETFL,flags);

驱动程序的流程(发送信号):

1、需要和应用进程进行关联-------信号要发送给谁
      实现fasync接口,在接口中进行关联
        int key_fasync (int fd , struct file * file, int no)
         {

                 //记录信号发送给哪个应用

                return fasync_helper(fd,file,no,&(key.fasync));       
          }
2、在合适位置发送信号
                kill_fasync(&(key.fasync),SIGIO,POLLIN);

下面看一下修改后的驱动程序是怎么样的

#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <asm/poll.h>

struct key_node
{
	unsigned int major;
	struct class * cls;
	struct device * dev;
	dev_t devno;
	wait_queue_head_t head;
	struct fasync_struct * fasync;
};

char data;
int condition = 0;
struct key_node key;

ssize_t key_read (struct file * file, char __user * buf, size_t size, loff_t * ops)
{
	
	if(((file->f_flags & O_NONBLOCK) != 0) && (condition == 0))//设置非阻塞
	{    //不管是阻塞还是非阻塞,只要有数据都要读取数据继续执行
		return -1;
	}
	wait_event_interruptible(key.head,condition);	//阻塞等待
	int ret;    //拷贝数据
	printk("key read\n");
	ret = copy_to_user(buf,&data,1);
	condition = 0;    //下一次能够阻塞
	data = '\0';    //把存储数据的缓冲区清空
	return 1;
}

int key_open (struct inode * inode, struct file * file)
{
	printk("key open\n");
	return 0;
}
int key_release (struct inode * inode , struct file * file)
{
	printk("key close\n");
	return 0;
}

irqreturn_t key_irq_handler(int irqno, void * dev)    //实现中断处理函数
{
	//获取到数据
	data = 'q';
	condition = 1;//变成非阻塞模式
	printk("input char '%c'\n",data);//可以认为是一个字符‘q’,也可以从数据寄存器中获取真实的值
	wake_up_interruptible(&(key.head));    //唤醒,把进程从等待队列中拿出来继续执行
	kill_fasync(&(key.fasync),SIGIO,POLLIN);	//发送信号
	return IRQ_HANDLED;
}

int key_fasync (int fd , struct file * file, int no)
{
	printk("key fasync \n");
	return fasync_helper(fd,file,no,&(key.fasync));//记录信号发送给哪个应用
}

const struct file_operations fops = {
	.open = key_open,
	.release = key_release,
	.read = key_read,
	.fasync = key_fasync,

};

static int __init key_drv_init(void)
{
	//1、申请设备号
	key.major = 230;
	register_chrdev(key.major,"key drv",&fops);
	//2、创建设备节点
	key.cls = class_create(THIS_MODULE,"cls");
	key.devno = MKDEV(key.major,0);
	key.dev = device_create(key.cls,NULL,key.devno,NULL,"key3");
	//4、硬件初始化
	//a、获取中断号
	struct device_node * node = of_find_node_by_path("/key3_node");	//获取设备树中的要使用的硬件节点
	int irqno = irq_of_parse_and_map(node,0);    //获取节点中的中断号
	//b、申请中断
	init_waitqueue_head(&(key.head));    //创建等待队列头
	request_irq(irqno,key_irq_handler,IRQF_TRIGGER_FALLING,"key interrupt",NULL);
	return 0;
}

static void __exit key_exit(void)
{
}

module_init(key_drv_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");

应用程序则是:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>

int fd;

void catch_signal(int signo)
{
	char num;
	if( read(fd,&num,1) < 0)
	{
		printf("no data;");
		return ;
	}
	printf("data is %c\n",num);
}

int main()
{
	fd = open("/dev/key3",O_RDONLY);//阻塞
	if(fd < 0)
	{
		perror("open error");
		return -1;
	}
	signal(SIGIO,catch_signal);	//1、设置信号处理方式
	fcntl(fd,F_SETOWN,getpid());	//2、设置当前进程为SIGIO信号的属主进程
	int flags = fcntl(fd,F_GETFL);	//3、将io模式设置为异步模式
	flags |= FASYNC;//添加异步属性
	fcntl(fd,F_SETFL,flags);
	//完成其他功能操作(没有按下时打印)
	while(1)
	{
		printf("hello world\n");
		sleep(1);
	}
	close(fd);
	return 0;
}

  5、分段处理中断数据(数据量耗时时用)

        中断实际上在处理上,实现的原则是“快进快出”,所以在遇见信息量太大,而无法完成快进快出的时候,则需要将信息进行分段处理。一部分先处理,剩下的在进行处理,也就是让处理器先处理一部分,然后处理器回到中断那里,而剩下的部分在进行处理。

则一般有下面三种方式(其实第二种方式已经包含了第一种方式)

        1、softirq:软中断,处理级别比较高,在内核机制中,需要修改内核源码功能
        2、tasklet:实际上就是内部调用了softirq
        3、workqueue:工作队列

tasklet:    

 初始化任务队列
void tasklet_init(struct tasklet_struct * t,void(* func)(unsigned long),unsigned long data)
参数1:struct tasklet_struct * t :任务队列头节点
参数2:void(* func)(unsigned long):下半部分的实现逻辑
参数3:unsigned long data:参数2函数的参数
在中断上半部分中,启动下半部分(放入内核线程中)
tasklet_schedule(struct tasklet_struct * t)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <asm/poll.h>

struct key_drv
{
	unsigned int major;
	struct class * cls;
	struct device * dev;
	struct tasklet_struct t;//任务队列头
	int irqno;
};

struct key_drv key;
irqreturn_t key_irq_handler(int irqno, void * dev)//中断处理--------中断上半部分
{

	printk("%s\n",(char *)dev);
	printk("key data\n");
	tasklet_schedule(&(key.t));
	return IRQ_HANDLED;
}

void key_func(unsigned long data)
{
	printk("data is %d\n",data);
}
const struct file_operations fops;
static int __init key_drv_init(void)
{
	//1、申请设备号
	key.major = 250;
	register_chrdev(key.major,"key int",&fops);
	//2、创建设备节点
	key.cls = class_create(THIS_MODULE,"cls");
	key.dev = device_create(key.cls,NULL,MKDEV(key.major,0),NULL,"key");
	//获取到设备号
	//获取设备树中的要使用的硬件节点
	struct device_node * node = of_find_node_by_path("/key3_node");
	//获取节点中的中断号
	key.irqno = irq_of_parse_and_map(node,0);
	//实现添加中断下半部分-------初始化任务队列
	tasklet_init(&(key.t),key_func,45);
	request_irq(key.irqno,key_irq_handler,IRQF_TRIGGER_FALLING,"key interrupt","key down");	//申请中断
	return 0;
}

static void __exit key_drv_exit(void)
{
	free_irq(key.irqno,"key down");    //中断释放
	device_destroy(key.cls,MKDEV(key.major,0));    //释放设备节点
	class_destroy(key.cls);    //释放结构体
	unregister_chrdev(key.major,"key int");    //释放设备号
}

module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");

workqueue:文章来源地址https://www.toymoban.com/news/detail-445924.html

三、系统总线

四、平台总线

到了这里,关于裸机开发之驱动开发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【ARM 裸机】汇编 led 驱动之烧写 bin 文件

    bin 文件烧写到哪里呢?使用 STM32 的时候烧写到内部 FLASH,6ULL 没有内部 FLASH,是不是就不能烧写呢?不,6ULL 支持 SD卡、EMMC、NAND FLASH、NOR FLASH 等方式启动,在裸机学习的工程中,选择 SD卡启动,所以将 bin 文件烧写到 SD卡中。 烧写并不是将 bin 文件直接拷贝到 SD卡中,而是

    2024年04月28日
    浏览(25)
  • I2C总线驱动:裸机版、应用层的使用、二级外设驱动三种方法

    SOC芯片平台的外设分为: 一级外设:外设控制器集成在SOC芯片内部 二级外设:外设控制器由另一块芯片负责,通过一些通讯总线与SOC芯片相连 Inter-Integrated Circuit: 字面意思是用于“集成电路之间”的通信总线,简写:IIC(或者I2C) i2c传输的要点就是: 传输一个字节 后面必然

    2024年02月09日
    浏览(26)
  • 快速理解DDD领域驱动设计架构思想-基础篇 | 京东物流技术团队

    本文与大家一起学习并介绍领域驱动设计(Domain Drive Design) 简称DDD,以及为什么我们需要领域驱动设计,它有哪些优缺点,尽量用一些通俗易懂文字来描述讲解领域驱动设计,本篇并不会从深层大论述讲解落地实现,这些大家可以在了解入门后再去深层次学习探讨或在后续进阶

    2024年02月09日
    浏览(35)
  • IMX6ULL裸机篇之中断实验-通用中断驱动说明二

    本文是 IMX6ULL 裸机篇---中断实验 。旨在用 C 语言编写一套简单的中断驱动框架代码。 在 start.S 文件中,我们在中断服务函数 IRQ_Handler 中调用了 C 函数 system_irqhandler 来处 理具体的中断。 本实验会认识中断控制器: GIC控制器。 I.MX6U(Cortex-A)的中断控制器,关于 GIC 的详细内容

    2023年04月24日
    浏览(43)
  • 驱动开发——嵌入式(驱动)软开基础(七)

    1 Linux驱动程序的功能是什么? (1)对设备初始化和释放。 (2)进行内核与硬件的数据交互。 (3)检测和处理设备出现的错误。 2. 内核程序中申请内存使用什么函数? 答案:kmalloc()、kzalloc()、vmalloc()。 解读: (1)void *kmalloc(size_t size, gfp_t flags); ①申请连续的物理内存,

    2024年02月06日
    浏览(84)
  • 驱动开发——嵌入式(驱动)软开基础(十)

    1. 64位的计算机有哪些优点? (1)可以进行更大范围的整数计算。 (2)可以支持更大的内存,虚拟内存空间大小一般为2^48(256TB)。64位的Linux一般使用48位表示虚拟内存空间地址,40位表示物理内存地址。 2. 中断分为哪两种? (1) 异步中断 :也叫 外部中断 ,由CPU外设产

    2024年02月06日
    浏览(41)
  • 1. 驱动开发--基础知识

    该文内容源于朱有鹏老师的课程,按照自己的理解进行汇总,方便查阅。如有侵权,请告知删除。 驱动一词的字面意思 物理上的驱动 硬件中的驱动 linux内核驱动   软件层面的驱动广义上就是指:这一段代码操作硬件去动,所以这一段代码就叫硬件的驱动程序。( 本质上

    2024年02月09日
    浏览(34)
  • 【ARM 裸机】开发环境搭建

    使用过程中,要频繁进行 Ubuntu 和 Windows 的文件互传,需要使用 FTP 服务; 1.1、开启 Ubuntu 下的 FTP 服务 修改结果,保证这两行命令前面没有 # ,保存退出; 重启 FTP 服务; 1.2、Windows 下安装 FTP 客户端 FileZilla 安装好 FileZilla 客户端之后,点击文件选项进入站点管理器,编号4输

    2024年04月10日
    浏览(35)
  • ARM裸机开发-串口通信

    一、在使用EXYNOS4412的串口发送和接收的时候,首先要对EXYNOS4412的串口进行配置,我们使用轮询方式时的配置有哪些? 1、配置GPIO,使对应管脚作为串口的发送和接收管脚   GPA0CON寄存器[7:4][3:0] 0x22    GPA0PUD寄存器[3:0] 0 禁止上下拉电阻 2、配置串口单元本身寄存器    

    2024年02月11日
    浏览(33)
  • [Linux_IMX6ULL驱动开发]-基础驱动

    如何理解嵌入式的驱动呢,我个人认为,驱动就是嵌入式上层应用操控底层硬件的桥梁。因为上层应用是在用户态,是无法直接操控底层的硬件的。我们需要利用系统调用(open、read、write等),进入内核态,通过打开对应的设备节点,通过read、write等通过编写的驱动函数来操

    2024年04月09日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包