最全Linux驱动开发八股文(五)

你好,我是拉依达。

这是我的Linux驱动开发八股文详细解析系列

本系列最开始是我在csdn上更新的文章,目前已经是csdn搜索“linux驱动”综合推荐第一名,累计阅读次数4w次。

全文总字数近8w字,是目前全网最全面,最清晰的入门linux驱动学习资料

现重新对内容进行整理,希望可以帮助到更多学习嵌入式的同学。

【下面是拉依达推荐学习相关专栏:】
一、Linux驱动学习专栏:拉依达的Linux驱动八股文 - 牛客网
二、Linux应用学习专栏:拉依达的Linux应用八股文 - 牛客网
【我的嵌入式学习和校招经验】 拉依达的嵌入式学习和秋招经验-CSDN博客
嵌入式学习规划/就业经验指导,可私信咨询

———————————————————————————————————————————————————

六、字符设备驱动

alt

6.1 字符设备基本驱动框架

1.模块加载

/* 驱动入口函数 */
static int __init xxx_init(void)
{
    /* 入口函数具体内容 */
    return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
    /* 出口函数具体内容 */
}
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit)

2.注册字符设备驱动

对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备。卸载驱动模块的时也需要注销掉字符设备。 字符设备的注册和注销函数原型:

static inline int register_chrdev(unsigned int major, 
									const char *name,
									const struct file_operations *fops)
 
static inline void unregister_chrdev(unsigned int major, 
									const char *name)

这种注册函数会将后面所有的次设备号全部占用,而且主设备号需要我们自己去设置,现在不推荐这样使用。 一般字符设备的注册驱动模块的入口函数 xxx_init 中进行,字符设备的注销驱动模块的出口函数 xxx_exit 中进行。

3.内存映射

  • 内存映射 在Linux中不能直接访问寄存器,要想要操作寄存器需要完成物理地址到虚拟空间的映射。
#define ioremap(cookie,size) __arm_ioremap((cookie), (size),
MT_DEVICE)
 
void __iomem * __arm_ioremap(phys_addr_t phys_addr,
						 	size_t size,
							unsigned int mtype)
{
    return arch_ioremap_caller(phys_addr, 
    							size, 
    							mtype,
    							__builtin_return_address(0));
}

返回值: __iomem 是编辑器标记,指向映射后的虚拟空间首地址。 建立映射:映射的虚拟地址 = ioremap(IO内存起始地址,映射长度); 一旦映射成功,访问对应的虚拟地址就相当于访问对应的IO内存 。

  • 解除映射
void iounmap (volatile void __iomem *addr)

4.应用层和内核层传递数据

应用层和内核层是不能直接进行数据传输的。 要想进行数据传输, 要借助下面的这两个函数

static inline long copy_from_user(void *to, const void __user * from, unsigned long n)
static inline long copy_to_user(void __user *to, const void *from, unsigned long n)

to:目标地址 from:源地址 n:将要拷贝数据的字节数 返回值:成功返回 0, 失败返回没有拷贝成功的数据字节数

5. 字符设备最基本框架

#define CHRDEVBASE_MAJOR 200			//手动设置主设备号
#define CHRDEVBASE_NAME  "chrdevbase"	//设备名称
//内核缓存区
static char readbuf[100];						//读数据缓存
static char writebuf[100];						//写数据缓存
static char kerneldata[] = {"kernel data!"};	//测试数据
//硬件寄存器
#define GPIO_TEST_BASE (0x01234567) 	//宏定义寄存器映射地址
static void __iomem *GPIO_TEST;			// __iomem 类型的指针,指向映射后的虚拟空间首地址
//打开设备
static int chrdevbase_open(struct inode *inode, struct file *filp) 
{
	return 0;
}
// 从设备读取数据 
static ssize_t chrdevbase_read(struct file *filp , char __user *buf , size_t cnt , loff_t *offt) 
{
	int retvalue = 0;
	unsigned char databuf[1];
// 读取硬件寄存器
#if 0  
	//读取寄存器状态
	databuf[0] = readl(GPIO_TEST);
	retvalue = copy_to_user(buf , databuf, cnt);
//读取内核内存
#else	
	//测试数据拷贝到读数据缓存中
    memcpy(readbuf , kerneldata , sizeof(kerneldata));  
    //内核中数据(读缓存)拷贝到用户空间
    retvalue = copy_to_user(buf , readbuf , cnt);
#endif

    if(retvalue == 0) printk("kernel senddate ok!\n");   
  	else printk("kernel senddate failed!\n");
    return 0;
}
//向设备写数据 
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt , loff_t *offt) 
{
	int retvalue = 0;
//写硬件寄存器
#if 0
	writel(buf[0],GPIO_TEST);
//写内核缓存
#else
	//用户数据拷贝到内核空间(写缓存)
    retvalue = copy_from_user(writebuf , buf ,cnt);
#endif
    if(retvalue == 0) printk("kernel recevdate : %s\n",writebuf);
  	else printk("kernel recevdate failed!");
    return 0;
}
//关闭/释放设备
static int chrdevbase_release(struct inode *inode , struct file *filp) 
{
	return 0;
}
//设备操作函数
static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,
    .open = chrdevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};
/* 驱动入口函数 */
static int __init chrdevbase_init(void)
{
	int retvalue = 0;
	//寄存器物理映射,物理地址映射到虚拟地址指针
	GPIO_TEST= ioremap(GPIO_TEST_BASE, 4);
	//注册字符设备驱动
    retvalue = register_chrdev(CHRDEVBASE_MAJOR, 	//主设备号
    							CHRDEVBASE_NAME, 	//设备名称
    							&chrdevbase_fops);	//设备操作函数集合
    							
    if(retvalue < 0) printk("chrdevbase driver register failed\n");
    printk("chrdevbase_init()\r\n");
    return 0;
}
/* 驱动出口函数 */
static void __exit chrdevbase_exit(void)
{
	//解除寄存器映射
	iounmap(GPIO_TEST);
	//注消字符设备驱动
	unregister_chrdev(CHRDEVBASE_MAJOR ,  	//主设备号
						CHRDEVBASE_NAME);	//设备名称
    printk("chrdevbase_exit()\r\n");
}
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

MODULE_LICENSE("GPI");//GPL模块许可证
MODULE_AUTHOR("songwei");//作者信息

6. 创建驱动节点文件

加载驱动模块后,需手动创建驱动节点文件

mknod /dev/chrdevbase c 200 0
  • 其中“mknod”是创建节点命令,
  • “/dev/chrdevbase”是要创建的节点文件,
  • “c”表示这是个字符设备,
  • “200”是设备的主设备号,
  • “0”是设备的次设备号。
  • 创建完成以后就会存在/dev/chrdevbase 这个文件,可以使用“ls /dev/chrdevbase -l”命令查看
#嵌入式##校招##八股文##Linux##linux驱动#
拉依达的Linux驱动八股文 文章被收录于专栏

你好,我是拉依达。 这是我的Linux驱动开发八股文详细解析系列。 本系列最开始是我在csdn上更新的文章,目前已经是csdn搜索“linux驱动”综合推荐第一名,累计阅读次数4w次。 全文总字数近8w字,是目前全网最全面,最清晰的入门linux驱动学习资料。 现在我重新对内容进行整理,已专栏的形式发布在牛客上,希望可以帮助到更多学习嵌入式的同学。

全部评论

相关推荐

点赞 评论 收藏
分享
2 收藏 评论
分享
牛客网
牛客企业服务