设备驱动程序 day62

四:设备驱动程序

在这里插入图片描述

1. 图解–流程

  1. 实现供应用程序调用的接口(open/write/read) 操作硬件的代码
  2. 向内核注册该驱动程序(以设备号的形式)
  3. 创建设备节点(一组绑定的设备名和设备号)

2.设备驱动分类

字符设备:一般以数据(字节)流的形式操作,数据访问有严格顺序

块设备:数据可以随机访问(eg:存储设备),以块的形式访问数据(类似数组,成块的字节操作)

网络设备:集成复杂的协议栈(eg:网卡) — 按名字维护

3.设备号

设备号:内核维护设备驱动程序的数字(每个设备都有唯一的设备号)

32位:

​ 高12位:主设备号 //区分不同类别的设备

​ 低20位:次设备号 //同类设备的不同设备

#define MKDEV(ma,mi)	((ma)<<8 | (mi))
cat /proc/devices	//查看注册的设备号

4. ctags

sudo app ctags
ctags -R	
    //追踪文件所属   ctrl + ]  vim界面下,和vscode查找差不多
    //ctrl + o  返回上次传送前的位置
    // <p 

5.代码

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>

#define MAJOR_NUM 255
#define MINOR_NUM 0
#define DEV_NAME "demo4"

static int open(struct inode * node,struct file * file)
{
    printk("demo4 open.....\n");
    return 0;
}

static ssize_t read(struct file * file,char __user * buf,size_t len,loff_t * offset)
{
    printk("demo4 read.....\n");
    return 0;
}

static ssize_t write(struct file * file,const char __user * buf,size_t len,loff_t * offset)
{
    printk("demo4 write.....\n");
    return 0;
}
static int close(struct inode * node,struct file * file)
{
    printk("demo4 close.....\n");
    return 0;
}

static dev_t dev_num;
static struct file_operations fops = 
{
    .owner = THIS_MODULE,
    .open  = open,
    .read  = read, 
    .write = write,
    .release = close
};

static struct cdev cdev;

static int __init demo_init(void)
{
    dev_num = MKDEV(MAJOR_NUM, MINOR_NUM);
    cdev_init(&cdev,&fops);
    cdev_add(&cdev,dev_num,1);
    register_chrdev_region(dev_num,1,DEV_NAME);

    printk("demo4_init ......\n");
    return 0;
}

static void __exit demo_exit(void)
{
    unregister_chrdev_region(dev_num,1);
    cdev_del(&cdev);
    printk("demo4_exit .......\n");
}

module_init(demo_init);
module_exit(demo_exit);

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#define MAJOR_NUM 253
#define MINOR_NUM 0
#define DEV_NAME "led4"
#define GPBCON 0x56000010 	//虚拟内存硬件不支持,硬件操作实际内存
#define GPBDAT 0x56000014
static volatile unsigned long * gpbcon;
static volatile unsigned long * gpbdat;

static void led2_init(void)
{
	*gpbcon &= ~(0x3 << 12);
	*gpbcon |= (0x1 << 12);
	*gpbdat |= (1 << 6);
}

static void led2_on(void)
{
	*gpbdat &= ~(1 << 6);
}

static void led2_off(void)
{
	*gpbdat |= (1 << 6);
}

//以下都是函数指针
static int open(struct inode * node, struct file * file)
{
	led2_init();
	printk("led4  open ...\n");	//内核打印
	return 0;
}

static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
	//copy_to_user();
	printk("led4 read ...\n");
	return 0;
}

static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
	unsigned char data[10] = {0};
	unsigned int len_cp = (sizeof(data) < len) ? sizeof(data) : len;
	ssize_t ret = len_cp;

	//strcmp(buf,"ledon")
	//copy_to_user	用户访问
	copy_from_user(data, buf, len_cp);	//让代码不要在内核空间运行去访问,如果数据有问题,内核访问野指针,内核崩溃
	if(!strcmp(data, "ledon"))
		led2_on();
	else if(!strcmp(data, "ledoff"))
		led2_off();
	else
		ret = -EINVAL;	//EINVAL “-” 返回负,返回非正常值

	printk("led4 write ...\n");
	return ret;
}

static int close(struct inode * node, struct file * file)
{
	led2_off();
	printk("led4 close ...\n");
	return 0;
}

static dev_t dev_num;	//创建设备号
//操作方法
static struct file_operations fops = 
{
	//结构体都是函数指针,上面函数指针都已经初始化了,
	//所以赋给对应的结构体中函数指针
	//gnu中,不像数据结构(windos)一个一个赋值,可以部分初始化
	.owner = THIS_MODULE,	//指向自己模块,默认
	.open = open,			//
	.read = read,
	.write = write,
	.release = close
};

static struct cdev cdev;	//设备的结构体 (要把设备号和操作方法放进去) 然后在给到内核

static int __init led_init(void)	
{
	int ret = 0;
	dev_num = MKDEV(MAJOR_NUM, MINOR_NUM);
	
	cdev_init(&cdev, &fops);	//cdev_init 把操作方法放进cdev结构体
	
	ret = cdev_add(&cdev, dev_num, 1);	//添加几个设备?--和对应设备号放进cdev结构体
	if(ret < 0)	//判断int类型
		goto err_cdev;

	ret = register_chrdev_region(dev_num, 1, DEV_NAME);	//注册字符设备,注册几个设备号?,设备名字
	if(ret < 0)
		goto err_register;

	gpbcon = ioremap(GPBCON, 4);	//内存映射四个字节,接受对应的物理地址
	gpbdat = ioremap(GPBDAT, 4);

	printk("led4_init   ....\n");
	return ret;

err_register:
	unregister_chrdev_region(dev_num, 1);	//取消注册
	printk("register_chrdev_region  failed\n");

err_cdev:
	cdev_del(&cdev);	//销毁初始化的cdev结构体
	printk("cdev_add  failed\n");
	return ret;	
}

static void __exit led_exit(void)
{
	iounmap(gpbdat);
	iounmap(gpbcon);
	unregister_chrdev_region(dev_num, 1);
	cdev_del(&cdev);
	printk("led4_exit   ....\n");
}

module_init(led_init);	//模块初始化 修饰对应的函数,然后执行对应函数
module_exit(led_exit);	//在操作系统注销时和卸载  -- 启动和注销


6.手动创建节点/

mknod  /dev/demo  c  255   0		//对应驱动程序
    
/dev/demo    设备节点名
c     字符设备
255   主设备号
0     次设备号
ls /dev
ls /dev -l    
6.1创建应用程序
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	int fd = open("/dev/led4",O_RDWR);	////打开对应内核文件
	if(fd < 0)
	{
		perror("open demo failed");
	}

	unsigned char buf[20] = {0};
	while(1)
	{
		write(fd,"ledon",strlen("ledon"));
		sleep(1);
		write(fd,"ledoff",strlen("ledoff"));
		sleep(1);
	}
	close(fd);

	return 0;
}
arm-linux-gcc demo_app.c	//之后在内核运行

7.编译

arm-linux-gcc demo4_app.c

在开发板中(minicom)./a.out
    //为了保护硬件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值