驱动入门的进一步深入

继上一篇驱动入门的进一步深入

*********************

注册字符设备驱动新接口

老接口:

ret = register_chrdev(MYMAJOR,MYNAME,&test_fops);

包括主设备号、名字、file_operations

新接口:

register_chrdev_region/alloc_chrdev_region + cdev_xx

register_chrdev_region:

需要指定设备号

alloc_chrdev_region :

内核分配设备号

cdev_xx:

包括cdev_init 、 cdev_add 、cdev_del等操作

设备号:

MKDEV、MAJOR、MINOR三个宏

MKDEV 返回值类型:dev_t类型

***************************************

指定设备号方法:

*

首先,定义static dev_t dev_id;

先用MKDEV申请一个设备号:

dev_id = MKDEV(MYMAJOR, 0); //设备号

MYMAJOR是指定的主设备号

后面的是次设备号

然后注册:

int register_chrdev_region(dev_t from, unsigned count, const char *name)

retval = register_chrdev_region(dev_id, MYCNT,MYNAME);

if (retval) {

printk(KERN_ERR "Unable to register minors for usb_device\n");

return retval;

}

用返回的dev_id

MYCNT:注册的数量

MYNAME:注册的名字

retval:返回值成功是0 ,失败返回负数

然后,注册驱动:

static struct cdev test_cdev;

static const struct file_operations test_fops

cdev_init(&test_cdev, &test_fops); //绑定两个结构体

retval = cdev_add(&test_cdev, dev_id, 1); //添加驱动

if (retval)

cdev_del(&test_cdev);

最后,注销

unregister_chrdev_region(dev_id, 1);

**************************************

上面就是完整的用新接口注册驱动的步骤

***********************

用内核自动分配设备号:

自动分配的话就不需要先用MKDEV申请设备号:

dev_id = MKDEV(MYMAJOR, 0); //设备号

而是直接用定义的结构体变量加上定义定义的子设备号,加上名字

就可以完成自动分配设备号

typedef u_long dev_t;(源结构体)

static dev_t mydev;

// 第1步:分配主次设备号

retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);

int alloc_chrdev_region(dev_t *dev, u[nsigned baseminor, unsigned count,

const char *name)

{

struct char_device_struct *cd;

cd = __register_chrdev_region(0, baseminor, count, name);

if (IS_ERR(cd))

return PTR_ERR(cd);

*dev = MKDEV(cd->major, cd->baseminor);

return 0;

}

*****************

这里对led灯的驱动操作

在模块层次代码中,应该要简洁,及对read进来的相关指令,这个指令应该是

简单的,例如1、2、3这样的,可以用宏定义

就是说,较为复杂的一些操作,例如对输入的指令的判断,用到的

strcmp等,应该在应用层完成

**********************

这里是对设备注册的一个操作

之前注册驱动时用到的是一个全局变量的结构体,

占据固定内存

static struct cdev test_cdev;

现在,用到

struct cdev *cdev_alloc(void)

{

struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);

if (p) {

INIT_LIST_HEAD(&p->list);

kobject_init(&p->kobj, &ktype_cdev_dynamic);

}

return p;

}

这一个函数,可以设置一个struct cdev类型的指针

struct cdev *cdev;

static struct cdev *test_cdev;

//注册驱动,使用cdev_alloc

test_cdev = cdev_alloc();

if (!test_cdev)

return -EINVAL;

test_cdev->owner = THIS_MODULE;

test_cdev->ops = test_fops;

//cdev_init(&test_cdev, &test_fops); //绑定两个结构体

这里使用的*指针的意义就是,用全局变量指定一个指针,只占一个指针大小的内存,

当需要使用时,就用cdev_alloc为指针动态分配一个堆内存,指向用的结构体,

需要注意的就是,在内核中,堆内存的释放一般都不是简单的对应关系,

它是内部有一个数的增加机制,有用到申请堆内存的就加一,用完就减一,

目前还不太了解其中机制

释放函数:

kfree(cls);

struct cdev {

struct kobject kobj;

struct module *owner;

const struct file_operations *ops;

struct list_head list;

dev_t dev;

unsigned int count;

};

这个就是用到的结构体,里面有指向模块、文件操作、链表、设备等

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

{

memset(cdev, 0, sizeof *cdev);

INIT_LIST_HEAD(&cdev->list);

kobject_init(&cdev->kobj, &ktype_cdev_default);

cdev->ops = fops;

}

这里不使用cdev_init,

而是直接实例化结构体其中的几个,

实际上也是和上面的函数是一样的,只不过是少实例几个而已

还有就是,上面实例化的owner

是在file_operation中的那个,

.owner = THIS_MODULE,

********************

总结:

以上的就是关于设备号的新旧使用,手动/自动分配使用,

关于设备注册的函数使用,

一个是全局变量直接定义,一个是定义一个全局变量的指针使用

关于设备的初始化,

可以用init函数,也可以直接实例化

*************

解决每次需要手动增添设备文件的不足:

手动添加设备文件:

root@sugar /root\]# mknod /dev/test c 250 0 解决方案: udev(嵌入式中使用mdev) udev 是 Linux 系统中的一个子系统,用于管理设备事件。 mdev : BusyBox 提供的轻量级设备管理工具,相当于精简版的 udev。 rootfs中Etc/init.d/rcS文件 echo /sbin/mdev \> /proc/sys/kernel/hotplug mdev -s echo /sbin/mdev \> /proc/sys/kernel/hotplug mdev -s 作用:初始化 /dev 目录下的设备节点。 原理: -s 参数让 mdev 扫描 /sys 文件系统,根据当前已连接的硬件信息, 在 /dev 目录下动态创建设备节点(如 /dev/sda、/dev/ttyUSB0 等) 作用:将 /sbin/mdev 设置为内核的 热插拔事件处理程序。 补充: /proc/sys/kernel/hotplug 的特殊性: 该文件存在于内存中(procfs),重启后失效, 需在启动脚本(如 /etc/rc.local)中持久化配置 完整 Linux 系统通常使用 udev 或 systemd-udevd, 但 mdev 更适合资源受限的嵌入式环境 总结: 这两条命令共同实现了 设备节点的动态创建/删除, 是嵌入式 Linux 自动识别硬件的基础 udev是应用层的一个应用程序 内核驱动和应用层udev之间有一套信息传输机制(netlink协议) 应用层启动udev,内核驱动中使用相应接口 驱动注册和注销时信息会传给udev,由udev在应用层进行设备 文件的创建和删除 使用相关函数添加设备文件: 1、class_create //源定义源函数 static struct class \*adb_dev_class; #define class_create(owner, name) \\ ({ \\ static struct lock_class_key __key; \\ __class_create(owner, name, \&__key); \\ }) owner:fos中的owner,指定类的所有者是哪个模块 name:自定义的class名字 //创建类 test_dev_class = class_create(THIS_MODULE, "sugar_class"); if (IS_ERR(test_dev_class)) return -EINVAL; //结束时销毁 void class_destroy(struct class \*cls) { if ((cls == NULL) \|\| (IS_ERR(cls))) return; class_unregister(cls); } class_destroy(test_dev_class); 2、device_create //设备文件创建,就是mknod 创建的文件名 struct device \*device_create(struct class \*class, struct device \*parent, dev_t devt, void \*drvdata, const char \*fmt, ...) { va_list vargs; struct device \*dev; va_start(vargs, fmt); dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs); va_end(vargs); return dev; } device_create(test_dev_class, NULL, dev_id, NULL, "test"); void device_destroy(struct class \*class, dev_t devt) { struct device \*dev; dev = class_find_device(class, NULL, \&devt, __match_devt); if (dev) { put_device(dev); device_unregister(dev); } } device_destroy(test_dev_class,dev_id); \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* 上面的使用就是一个设备类的创建, 类中的一个设备文件的创建(就是mknod创建的设备文件的名字) 结合: 创建: class_create() device_create() 销毁: device_destroy() class_destroy() \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* 成功现象: \[root@sugar /root\]# insmod module_test.ko \[ 39.180515\] major = 250, minor = 0. \[root@sugar /root\]# lsmod Not tainted module_test 2102 0 - Live 0xbf000000 \[root@sugar /root\]# ls /dev/test -l crw-rw---- 1 root 0 250, 0 Jan 1 12:00 /dev/test \[root@sugar /root\]# ./app 直接安装执行,不需要再手动mknod创建设备文件 insmod后直接查看也可以查看到device_destroy中输入的test文件 \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* 查看创建的class \[root@sugar /sys\]# cd class/ \[root@sugar class\]# ls sugar_class \[root@sugar class\]# cd sugar_class/ \[root@sugar sugar_class\]# ls test \[root@sugar sugar_class\]# cd test/ \[root@sugar test\]# ls dev power subsystem uevent 用cat查看,可以查看里面的相关信息 \[root@sugar test\]# cat uevent MAJOR=250 MINOR=0 DEVNAME=test \[root@sugar test\]# cat dev 250:0 函数代码分析: class_create() __class_create(owner, name, \&__key); //实际调用 cls = kzalloc(sizeof(\*cls), GFP_KERNEL);//申请堆内存 __class_register //注册 cp = kzalloc(sizeof(\*cp), GFP_KERNEL); kset_register kobject_uevent struct kobject \*kobj //输出型参数,作为返回值 返回的就是kobject结构体的相应参数 struct kobject { const char \*name; struct list_head entry; struct kobject \*parent; struct kset \*kset; struct kobj_type \*ktype; struct sysfs_dirent \*sd; struct kref kref; unsigned int state_initialized:1; unsigned int state_in_sysfs:1; unsigned int state_add_uevent_sent:1; unsigned int state_remove_uevent_sent:1; unsigned int uevent_suppress:1; }; kfree(cls); device_create() device_create_vargs dev = kzalloc(sizeof(\*dev), GFP_KERNEL); dev-\>devt = devt; dev-\>class = class; dev-\>parent = parent; dev-\>release = device_create_release; dev_set_drvdata(dev, drvdata); kobject_set_name_vargs device_register device_initialize device_add kobject_add device_create_file device_create_sys_dev_entry devtmpfs_create_node device_add_class_symlinks device_add_attrs bus_add_device dpm_sysfs_add device_pm_add //power management kobject_uevent //输出结构体相关参数 kfree(dev-\>p); \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* 以上就是两个类和设备文件的函数分析 \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* 新旧字符设备号的注册: register_chrdev register_chrdev_region/alloc_chrdev_region register_chrdev __register_chrdev(major, 0, 256, name, fops);//实际调用 __register_chrdev_region(major, baseminor, count, name); if (major == 0) { for (i = ARRAY_SIZE(chrdevs)-1; i \> 0; i--) { if (chrdevs\[i\] == NULL) break; }//这里的处理就是解释到为什么设备号的分配从后往前分配 register_chrdev_region __register_chrdev_region(MAJOR(n), MINOR(n), next - n, name); alloc_chrdev_region __register_chrdev_region(0, baseminor, count, name); 总结: 实际上,三个都是调用的__register_chrdev_region这个函数, 只不过是里面的传进去的参数的定义不同 新的这个两个主要是设备号定义,为0 旧的是无法定义子设备号 \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* 静态页表的分析: 之前对寄存器的操作有: 静态映射、动态映射两种方法 静态映射: 使用的就是自定义好的寄存器地址相关的虚拟地址 其中,涉及到的一个就是静态映射表, 有了这个表才能实现一段物理地址到虚拟地址的映射关系 1、映射表涉及到的物理地址和虚拟地址的值的相关宏定义 2、映射表的建立函数 /kernel/arch/arm/mach-s5pv210/mach-smdkc110.c smdkc110_map_io s5p_init_io(NULL, 0, S5P_VA_CHIPID); iotable_init(s5p_iodesc, ARRAY_SIZE(s5p_iodesc)); create_mapping 上面函数涉及到的就是io表的初始化,创建映射 iotable_init中涉及到的映射有s5p_iodesc 这是一个结构体数组,数组中的每一个元素就是一个映射, 描述了一段物理地址到虚拟地址之间的映射 表中记录的这些映射关系被iotable_init调用,这个函数负责将这个结构体 数组格式的表建立称MMU所能识别的页表映射关系,这样开机后就可以直接 使用对应的虚拟地址访问对应的物理地址 其中,页表显示都是4k、8k、16k... arch/arm/plat-s5p/cpu.c 映射表对应的结构体数组: /\* Initial IO mappings \*/ static struct map_desc s5p_iodesc\[\] __initdata = { { { .virtual = (unsigned long)S5P_VA_GPIO, .pfn = __phys_to_pfn(S5P_PA_GPIO), .length = SZ_4K, .type = MT_DEVICE, }, 这块就是我们要操作的gpio对应的映射关系转换模块 还有一个问题,就是开机时调用映射表建立函数 开机时(kernel启动时)smdkc110_map_io怎么被调用的 start_kernel(main.c) setup_arch(573) paging_init devicemaps_init /\* \* Ask the machine support to map in the statically mapped devices. \*/ if (mdesc-\>map_io) mdesc-\>map_io(); \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* 动态映射 这里解决一个问题,就是动态映射对多个寄存器需要多次映射的问题 解决: 仿效真实驱动中,用结构体封装的方式来进行单次多个寄存器的地址映射 这样就可以不用多次进行单个寄存器的映射了 //定义一个结构体包含两个寄存器 typedef struct GPJ0REG{ volatile unsigned int gpj0_con; volatile unsigned int gpj0_dat; }gpj0_refs; 注意: 定义结构体不能用static, 如果我们在文件作用域内使用\`static\`关键字修饰一个结构体定义, 这是不允许的,编译器会报错。 因为结构体定义本身并不占用存储空间,它只是一个类型描述 还有,结构体内定义的变量不要与外部变量名字有冲突,否则有错 #define GPJ0_PA_BASE 0xe0200240 gpj0_refs \*pGPJ0_REG; //用两步完成动态映射 if (!request_mem_region(GPJ0_PA_BASE, sizeof(gpj0_refs), "GPJ0REG")) return -EINVAL; pGPJ0_REG=ioremap(GPJ0_PA_BASE, sizeof(gpj0_refs)); //销毁 iounmap(pGPJ0_REG); release_mem_region(GPJ0_PA_BASE, sizeof(gpj0_refs)); \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* 最后,是一个writel、readl函数 用来操作寄存器(当完成映射后) writel() #define writel(b,addr) __raw_writel(__cpu_to_le32(b),addr) __raw_writel #define __raw_writel(v,a) (__chk_io_ptr(a), \*(volatile unsigned int __force \*)(a) = (v)) 这个就是实际工作的函数 实质就是我们之前设置的一个定义即操作的寄存器地址+要进行的操作 #define rGPJ0CON \*((volatile unsigned int \*)S5PV210_GPJ0CON) #define rGPJ0DAT \*((volatile unsigned int \*)S5PV210_GPJ0DAT) #define GPJ0CON_PA 0xe0200240 static void __iomem \*baseaddr; // 寄存器的虚拟地址的基地址 通过ioremap 得到虚拟地址 writel(0x11111111, baseaddr+0); writel(((0\<\<3) \| (0\<\<4) \| (0\<\<5)), baseaddr+4); iowrite32() #define iowrite32(v, addr) writel((v), (addr)) writel 实际就是上面的函数 \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* 至此,可以完成一个led的驱动 其中,包括使用 1、新旧设备号注册函数、驱动的alloc申请(alloc_chrdev_region) 2、对test_cdev的一个操作,一个是用全局变量做全部的初始化, 一个使用一个全局指针,后面对齐实例化操作(替换一个cdev_init()函数) //static struct cdev test_cdev; static struct cdev \*test_cdev; 3、类的创建 目的就是不用手动mknod取创建设备文件 class_create device_create 4、对动态映射的申请优化,使用一个指针接收虚拟地址映射, 可以用结构体定义其中的寄存器,亦可以自己在地址中加上相应的偏移量 request_mem_region ioremap 5、对寄存器的操作 iowrite32 writel

相关推荐
Raners_4 分钟前
【Linux】文件权限以及特殊权限(SUID、SGID)
linux·安全
egoist20237 分钟前
【Linux仓库】进程优先级及进程调度【进程·肆】
linux·运维·服务器·进程切换·进程调度·进程优先级·大o1调度
m0_4666077012 分钟前
【STM32CubeMX】ST官网MCU固件库下载及安装
stm32·单片机·嵌入式硬件
2301_1472583691 小时前
7月2日作业
java·linux·服务器
Wallace Zhang4 小时前
STM32F103_Bootloader程序开发11 - 实现 App 安全跳转至 Bootloader
stm32·嵌入式硬件·安全
GodKK老神灭4 小时前
STM32 CCR寄存器
stm32·单片机·嵌入式硬件
xuanzdhc6 小时前
Linux 基础IO
linux·运维·服务器
愚润求学6 小时前
【Linux】网络基础
linux·运维·网络
bantinghy6 小时前
Linux进程单例模式运行
linux·服务器·单例模式
小和尚同志7 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维