1.平台总线模型也叫 platform 总线模型。是 Linux 内核虚拟出来的一条总线,不是真实的导线。
平台总线模型把原来的驱动 C 文件给分成了两个 C 文件,一个是和硬件相关的 device.c,一个是和驱动相关的 driver.c。
把稳定不变的放在 driver.c 里面,需要变的就放在了 device.c 里面。
2. 平台总线模型的优点:
(1)可以提高代码的重用性
(2)减少重复性代码
3.以平台总线模型设计的驱动,要分别注册 device.c 和 driver.c 。
平台总线是以名字来匹配的,实际上就是字符串比较。
PART ONE.注册 platform 设备
1.平台总线注册一个 device :
device.c 里面写的是硬件资源,这里的硬件资源是指寄存器的地址, 中断号, 时钟等硬件资源。
在 Linux 内核里,我们使用 platform_device 结构体来描述硬件资源。
struct platform_device {
const char * name;
//平台总线进行匹配的时候用到的name,会在/sys/bus/platform/devices下生成
int id;
//设备id,一般填-1
struct device dev;
//内嵌的device结构体,设备通用属性
u32 num_resources;
//资源的数量
struct resource * resource;
//device里的硬件资源
};
2. 描述硬件资源使用的结构体:
struct resource {
resource_size_t start;
//资源的起始
resource_size_t end;
//资源的结束
const char *name;
//资源的名字,可选
unsigned long flags;
//资源的类型
struct resource *parent, *sibling, *child;
//一些节点
};
3.常用的资源的类型:
#define IORESOURCE_IO 0x00000100
IO的内存
#define IORESOURCE_MEM 0x00000200
常用于表述一段寄存器的地址,物理内存
#define IORESOURCE_IRQ 0x00000400
常用于表述中断号
#define IORESOURCE_DMA 0x00000800
DMA地址
#define IORESOURCE_BUS 0x00001000
总线号
4.视频中描述的是蜂鸣器用到的GPIO引脚的资源,使用ULL的开发板,到相关的使用手册上找到数据寄存器的地址。只控制了数据寄存器,没有设置电器属性,复用关系,在ULL系统启动时加载了设备树,把一系列的配置都给配好了。如果没有配置的话就要手动配置复用寄存器、电器属性寄存器、方向寄存器、数据寄存器等。
5.注册与注销:
platform_device_register(struct platform_device *pdev);
platform_device_unregister(struct platform_device *pdev);
6. 上代码,上代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
void beep_release(struct device *dev)
{
printk("beep_release\n");
}
//描述硬件资源使用的结构体
struct resource beep_res[] = {
[0] = {
.start = 0x20AC000,
.end = 0x20AC003,
.flags = IORESOURCE_MEM,
.name = "GPIO5_DR"
}
};
struct platform_device beep_device = {
.name = "beep_test",
.id = -1,
.resource = beep_res,
.num_resources = ARRAY_SIZE(beep_res),
.dev = {
.release = beep_release
}
};
static int __init device_init(void)
{
printk("device_init\n");
return platform_device_register(&beep_device);
}
static void __exit device_exit(void)
{
printk("device_exit\n");
platform_device_unregister(&beep_device);
}
module_init(device_init);
module_exit(device_exit);
MODULE_LICENSE("GPL");
PART TWO.注册 platform 驱动
1.编写 driver.c 的思路
首先定义一个 platform_driver 结构体变量,然后去实现结构体中的各个成员变量,当 driver 和 device 匹配成功时,就会执行 probe 函数,重点就在于 probe 函数的编写。
2. platform_driver 结构体:
struct platform_driver {
int (*probe)(struct platform_device *);
//当driver和device匹配成功的时候就会执行probe函数
int (*remove)(struct platform_device *);
//当driver和device任意一个remove的时候会执行
void (*shutdown)(struct platform_device *);
//当设备收到shutdown命令时执行
int (*suspend)(struct platform_device *, pm_message_t state);
//当设备收到休眠命令时执行
int (*resume)(struct platform_device *);
//当设备收到唤醒命令时执行
struct device_driver driver;
const struct platform_device_id *id_table;
//优先匹配id_table中的name
};
struct device_driver {
const char *name; //进行匹配设备时候用到的名字
struct module *owner; //THIS_MODULE
};
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
3.同样的也是需要注册和注销:
int platform_driver_register(struct platform_driver *drv);
void platform_driver_unregister(struct platform_driver *drv);
4. 来了来了,代码来了:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
int beep_probe (struct platform_device *pdev)
{
printk("beep_probe\n");
return 0;
};
int beep_remove (struct platform_device *pdev)
{
printk("beep_remove\n");
return 0;
};
const struct platform_device_id beep_id_table = {
.name = "beep_test"
};
struct platform_driver beep_device = {
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "beep_test"
},
.id_table = &beep_id_table
};
static int __init beep_driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&beep_device);
if(ret < 0)
{
printk("platform_driver_register error\n");
return ret;
}
printk("platform_driver_register ok\n");
return 0;
}
static void __exit beep_driver_exit(void)
{
printk("hello_exit\n");
platform_driver_unregister(&beep_device);
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");
PART THREE.编写 probe 函数
1.编写 probe 函数的思路:
(1)从 device.c 里面获得硬件资源
(2)注册杂项/字符设备,完善 file_operation 结构体,并生成设备节点
2.获得硬件资源的两种方式:
方式一:从device.c里面直接获得硬件资源,不推荐。
printk("beep_res is %s\n",pdev->resource[0].name);
方式二:使用函数获得,推荐。
extern struct resource *platform_get_resource
(struct platform_device *,unsigned int, unsigned int);
//参数:pdev形参,资源的类型,资源的位置(处在同类资源的第几个)
3.注册之前要先登记到内核:
request_mem_region(start, n, name);
//参数:起始地址,长度,名字
4.代码,代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
struct resource *beep_mem;
struct resource *beep_mem_tmp;
unsigned int *vir_pwm_dr;
int beep_open(struct inode * inode, struct file * file)
{
printk("beep_open\n");
return 0;
}
int beep_release (struct inode *inode, struct file *file)
{
printk("beep_release\n");
return 0;
}
ssize_t beep_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[64] = "hahaha";
if(copy_to_user(ubuf, kbuf, strlen(kbuf)) != 0)
{
printk("copy_to_user error\n");
return -1;
}
return 0;
}
ssize_t beep_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)
{
printk("copy_from_user error\n");
return -1;
}
printk("beep_write\n");
if(kbuf[0] == 1)
*vir_pwm_dr |= (1<<1);
else if(kbuf[0] == 0)
*vir_pwm_dr &= ~(1<<1);
return 0;
}
struct file_operations beep_fops = {
.owner = THIS_MODULE,
.open = beep_open,
.release = beep_release,
.read = beep_read,
.write = beep_write
};
struct miscdevice beep_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "beep_drv",
.fops = &beep_fops
};
int beep_probe (struct platform_device *pdev)
{
int ret = 0;
printk("beep_probe\n");
/*方法一:从device.c里面直接获得硬件资源,不推荐*/
//printk("beep_res is %s\n",pdev->resource[0].name);
/*方法二:使用函数获得*/
//extern struct resource *platform_get_resource
//(struct platform_device *,unsigned int, unsigned int);
//形参pdev,资源类型,是同类资源的第几个
beep_mem = platform_get_resource(pdev,IORESOURCE_MEM,0);
if(beep_mem == NULL){
printk("platform_get_resource error\n");
return -EBUSY;
}
printk("platform_get_resource ok\n");
printk("beep_res start is 0x%x\n",beep_mem->start);
printk("beep_res end is 0x%x\n",beep_mem->end);
#if 0
//写这部分会登记失败,因为内核已经登记过了,正常是要写
//登记到内核,起始地址,长度,名字
beep_mem_tmp = request_mem_region(beep_mem->start,
beep_mem->end - beep_mem->start + 1,"beep");
if(beep_mem_tmp == NULL){
printk("request_mem_region error\n");
//没有登记成功就要释放掉
goto err_region;
};
return 0;
err_region:
release_mem_region(beep_mem->start, beep_mem->end - beep_mem->start + 1);
return -EBUSY;
#endif
/****************************************************/
//映射虚拟地址(物理地址,要映射的大小)
vir_pwm_dr = ioremap(beep_mem->start, 4);
if(vir_pwm_dr == NULL)
{
printk("PWM ioremap error\n");
return -EBUSY;
}
printk("PWM ioremap ok\n");
/****************************************************/
ret = misc_register(&beep_dev);
if(ret < 0)
{
printk("beep register is error\n");
return -1;
}
printk("beep_init\n");
/****************************************************/
return 0;
};
int beep_remove (struct platform_device *pdev)
{
printk("beep_remove\n");
return 0;
};
const struct platform_device_id beep_id_table = {
.name = "beep_test"
};
struct platform_driver beep_device = {
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "beep_test"
},
.id_table = &beep_id_table
};
static int __init beep_driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&beep_device);
if(ret < 0)
{
printk("platform_driver_register error\n");
return ret;
}
printk("platform_driver_register ok\n");
return 0;
}
static void __exit beep_driver_exit(void)
{
printk("hello_exit\n");
platform_driver_unregister(&beep_device);
misc_deregister(&beep_dev);
iounmap(vir_pwm_dr);
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");