迅为嵌入式linux驱动开发笔记(二)—地址映射与字符设备_void __iomem(2)

#include <linux/uaccess.h>
#include <linux/io.h>

#define GPIO5_DR 0x020AC000//查看数据手册,寄存器DR的地址

unsigned int *vir_gpio_dr;//存放映射完成的虚拟地址的首地址

int misc_open(struct inode *inode,struct file *file)
{
printk(“hello misc_open\n”);

return 0;

}

int misc_close(struct inode *inode,struct file *file)
{
printk(“bye bye\n”);

return 0;

}

int misc_read(struct file *file,char __user *ubuf,size_t size,loff_t *loff_t)
{
char kbuf[64] = “hello”;

if(copy\_to\_user(ubuf,kbuf,sizeof(kbuf))!=0)
{
    printk("copy to user error\n");
    return -1;
}  
return 0;

}

int misc_write(struct file *file,const char __user *ubuf,size_t size,loff_t *loff_t)
{
char kbuf[64] = {0};

if(copy\_from\_user(kbuf,ubuf,size)!=0)  //ubuf 传送进来 保存到 kbuf中
{
    printk("copy\_from\_user\n");
    return -1;
}  

if (kbuf[0] == 0)
    \*vir_gpio_dr |= (1 <<1);//关闭
else if (kbuf[0] == 1)
    \*vir_gpio_dr &= ~(1 <<1);//打开
    
return 0;

}

struct file_operations misc_fops ={
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_close,
.read = misc_read,
.write = misc_write
};

struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = “hello_misc”,
.fops = &misc_fops
};

static int misc_init(void)
{
int ret;

ret = misc\_register(&misc_dev);//注册杂项设备

if (ret<0)
{
    printk("misc\_register is error\n");
    return -1;
}
printk("misc\_register is successful\n");

vir_gpio_dr = ioremap(GPIO5_DR,4);//指定地址映射4个字节大小

if(vir_gpio_dr == NULL) 
{
    printk("GPIO5\_DR ioremap error\n");
    return -EBUSY;
}
printk("GPIO5\_DR ioremap ok\n");

return 0;

}

static void misc_exit(void)
{
misc_deregister(&misc_dev);//卸载

printk("bye bye\n");

iounmap(vir_gpio_dr);//取消映射

}

module_init(misc_init);

module_exit(misc_exit);

MODULE_LICENSE(“GPL”);


应用代码



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

int main(int argc, char **argv)
{
int fd;

char buf[64] = {};

fd = open("/dev/hello\_misc",O_RDWR);
if (fd < 0) 
{
    printf("open error\n");
    
    return fd;
}

buf[0] = atoi(argv[1]);

write(fd, buf, sizeof(buf));//向驱动层写入,驱动层调用misc\_write

close(fd);

return 0;

}


#### 驱动模块传参


1、什么是驱动传参


驱动传参就是传递参数给驱动。  
 举例:  
 insmod beep.ko a=1


2、驱动传参有什么作用?


(1)设置驱动相关参数,比如设置缓冲区的大小


(2)设置内核的安全校验,防止写的驱动被别人盗用


3、怎么给驱动传递参数?  
 (1)传递普通的参数,比如char,int类型


函数:  
 module\_param(name,type,perm);  
 参数:  
 name 要传递进去参数的名称  
 type:类型  
 perm:参数读写的权限



static int a;

module_param(a,int,S_IRUSR);

static int hello_init(void)
{
printk(“a=%d\n”,a);

return 0;

}


(2)传递数组  
 函数:  
 module\_param\_array(name,type,numo,perm);  
 参数:  
 name:需要传递的参数  
 type:传递的参数类型  
 numo:实际传入进入的个数  
 perm:参数读写的权限



static int b[5];

static int count;

module_param(a,int,S_IRUSR);

module_param_array(b,int,&count,S_IRUSR);

static int hello_init(void)
{
int i;
for (i=0;i<count;i++)
{
printk(“b[i]=%d\n”,i,b[i]);
}

printk("count = %d\n",count);

printk("a=%d\n",a);

return 0;

}


使用命令传入参数


##### 实验现象


实验结果如下图所示:打印出a的数值  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/ee51d2cfa1504e269204ce8d3e27e085.png)  
 实验结果如下图所示:打印出b的数组的数值



insmod parameter.ko b=1,2,1,3,3


![在这里插入图片描述](https://img-blog.csdnimg.cn/e5860f543c424f478979afad99cf3677.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5qKF5bGx5YmR5a6i,size_20,color_FFFFFF,t_70,g_se,x_16)


(3)如果传递参数超过个数,会发生什么?


会报错,崩溃~~~!!!如图所示!!!


![在这里插入图片描述](https://img-blog.csdnimg.cn/a299173ad04c4f03945ee19a25168b0d.png)


#### 二、申请字符类设备号


1、字符设备和杂项设备的区别(复习)


杂项设备的主设备号是固定的,固定为10。


字符设备的主设备号不是固定的,那么需要自己或者系统来分配设备号。


杂项设备可以自动生成设备节点。


字符设备需要手动生成设备节点。


2、注册字符类设备号的两个方法。


第一种:静态分配一个设备号


在include/linux/fs.h函数:



int register_chrdev_region(dev_t, unsigned, const char *);


需要明确知道系统里面哪些设备号没有被使用。  
 参数:  
 dev\_t 设备号的起始值。类型是dev\_t。  
 unsigned:次设备号的个数。  
 const char\* : 设备号的名称。  
 返回值:成功返回0,失败返回非0。


dev\_t 用来保存设备号,是一个32位的数据类型。定义在linux/types.h中。


高12位用来保存主设备号。


低20位用来保存次设备号。


Linux提供的几个宏定义来操作设备号。



//次设备号的位数,一共是20位
#define MINORBITS 20

//次设备号的掩码
#define MINORMASK ((1U << MINORBITS) - 1)

//dev_t中获取主设备号
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))

//dev_t中获取次设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))

//将主设备号和次设备号组成dev_t类型,第一个参数是主设备号,第二个参数是次设备号。
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))


第二种:动态分配


使用函数为:



int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);


参数:  
 第一个:保存生成的设备号。


第二个:请求的第一个次设备号,通常为0。


第三个:连续申请的设备号的个数。


第四个:设备名称。


返回值:成功返回0,失败返回非0。


使用动态分配会优先使用255-234之间的号码。


3、注销设备号


参数1:分配设备号的起始地址。


参数2:申请设备号的数目。



void unregister_chrdev_region(dev_t,unsigned);


驱动代码编写如下所示:分为两种设备号分配方式(静态分配和动态分配)



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

static int major_num,minor_num;

#define DEVICE_NUMBER 1 //分配设备号数目
#define DEVICE_SNAME “schrdev” //主设备号名称
#define DEVICE_ANAME “achrdev” //次设备号名称

#define DEVICE_MIN_NUMBER 0 //次设备号起始位置

module_param(major_num,int,S_IRUSR);

module_param(minor_num,int,S_IRUSR);

static int hello_init(void)
{
dev_t dev_num;

int ret;

printk("major\_num = %d\n",major_num);

printk("minor\_num = %d\n",minor_num);    

if (major_num)//主设备号传递进来,静态方法
{
    dev_num = MKDEV(major_num,minor_num);

    ret = register\_chrdev\_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME);
    if (ret<0)
    {
        printk("register\_chrdev\_region error\n");
    }
    printk("register\_chrdev\_region ok\n");
}
else //主设备号没有传递进来,使用动态方法分配设备号
{
    ret = alloc\_chrdev\_region(&dev_num,DEVICE_MIN_NUMBER,DEVICE_NUMBER,DEVICE_ANAME);
    if (ret<0)
    {
        printk("register\_chrdev\_region error\n");
    }
    printk("register\_chrdev\_region ok\n");

    major_num = MAJOR(dev_num);

    minor_num = MINOR(dev_num);

    printk("major\_num = %d\n",major_num);

    printk("minor\_num = %d\n",minor_num);

}

return 0;

}

static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);//注销设备号
printk(“bye bye”);
}

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE(“GPL”);


建议使用动态分配的方式申请设备号。静态分配在多人使用容易冲突。


##### 实验现象


申请字符类设备号之后,打印主设备号和次设备号。之后在注册设备号成功之后,打印主设备号和次设备号。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/87f75e77ec0c4be1b8a0af064043374c.png)


#### 三、注册字符类设备


注册杂项设备  
 misc\_register(&misc\_dev);


注销杂项设备  
 misc\_deregister(&misc\_dev);


cdev结构体:描述字符设备的结构体


在include/linux/cdev.h中



struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};


步骤一:定义一个cdev结构体


步骤二:使用cdev\_init函数初始化cdev结构体成员



void cdev_init(struct cdev *, const struct file_operations *);


参数:  
 第一个:要初始化的cdev


第二个:文件操作集


cdev->ops = fops;//实际就是把文件操作集写给ops。


步骤三:使用cdev\_add函数注册到内核中



int cdev_add(struct cdev *, dev_t, unsigned);


参数1:cdev的结构体指针  
 参数2:设备号  
 参数3:次设备号的数量  
 注销字符设备:



void cdev_del(struct cdev *);


驱动代码编写



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

struct cdev cdev;

static int hello_init(void)
{
上节代码添加如下内容
.
.
.
.
.
.
cdev.owner = THIS_MODULE;

cdev\_init(&cdev,&chrdev_ops);//字符设备初始化

cdev\_add(&cdev,dev_num,DEVICE_NUMBER);//字符设备的注册

return 0;

}

static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);

cdev\_del(&cdev);

printk("bye bye");

}


应用代码编写



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

int main(int argc, char **argv)
{
int fd;

char buf[64] = {};

fd = open("/dev/test",O_RDWR);
if (fd < 0) 
{
    printf("open error\n");
    return fd;
}
return 0;

}


##### 注意!!


字符设备注册完成之后,并不会自动生成设备节点,需要使用mknod命令创建一个设备节点。  
 格式:



mknod 名称 类型 主设备号 次设备号


举例:



mknod /dev/test c 247 0


创建设备节点:  
 创建完成之后,查看/dev/目录 可以看到test  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/936154ef8b82425ebba5a1124213da8d.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5qKF5bGx5YmR5a6i,size_20,color_FFFFFF,t_70,g_se,x_16)运行app


![在这里插入图片描述](https://img-blog.csdnimg.cn/c5d02ef802214c9cb169bc19184d1382.png)  
 现象:运行app之后,打印驱动open调用结果。


#### 四、自动创建设备节点(推荐)


## 最后

**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。**

**因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

![img](https://img-blog.csdnimg.cn/img_convert/c89d122786c07372f880a67074028a5b.png)

![img](https://img-blog.csdnimg.cn/img_convert/92935d9baf6200d022cdd3e9d1195135.jpeg)

![img](https://img-blog.csdnimg.cn/img_convert/0f5392c3f2681e7e5bfbc91e60dbbfd2.png)

 ![img](https://img-blog.csdnimg.cn/img_convert/fb16bde63acc6b4e4c695b9822ae2a81.png)

![img](https://img-blog.csdnimg.cn/img_convert/7b05a068486b79f4f653b0ba536279b1.png)

![img](https://img-blog.csdnimg.cn/img_convert/e64ab6922d40247ba422824ef2cdb99c.png)

![](https://img-blog.csdnimg.cn/img_convert/9f2778c13f253b6f271aaf78bf713fed.png)

 

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!**

[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618654289)

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**!!


提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。**

**因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中...(img-Psv1lOGW-1715840721918)]

[外链图片转存中...(img-wsxsbVfn-1715840721919)]

[外链图片转存中...(img-36j6UIAL-1715840721919)]

 [外链图片转存中...(img-V9DUiXZk-1715840721920)]

[外链图片转存中...(img-m8YGpH0Z-1715840721920)]

[外链图片转存中...(img-Z87zQ5s9-1715840721921)]

[外链图片转存中...(img-BVrZeh9f-1715840721921)]

 

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!**

[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618654289)

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**!!


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值