一.linux內核platform機制
1.1.回顧ioremap實現開關燈驅動需求:
硬件LED的GPIO由GPC0_3,GPC0_4更換爲GPF1_4,GPF1_5
通過分析代碼可知:
1.一個完整的驅動包括兩部分:純硬件和純軟件
2.之前的驅動如果硬件發生變化,代碼幾乎重頭都要進行檢查和修改,增大了驅動開發的工作量,可移植性非常差;
3.如果要優化此問題,內核提出分離思想,把純軟件和純硬件的信息進行分離,將來驅動開發者的重心放在純硬件上,軟件一經寫好,無需在改動;
1.2.linux內核分離思想
將硬件和軟件進行分離;
分離思想的實現依靠platform機制,platform依賴設備-總線-驅動編程模型;
1.3.設備-總線-驅動編程模型:
實現的步驟參見PPT即可
總結:如果要採用設備-總線-驅動編程模型實現一個設備驅動,只需關注:
struct platform_device
struct platform_driver
問:如何使用以上兩個數據結構呢?
1.4.struct platform_device
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource *resource;
...
}
struct device {
void *platform_data;
};
struct resource {
resource_size_t start; //硬件的起始信息
resource_size_t end;//硬件的結束信息
unsigned long flags; //硬件信息的標誌
有兩種標誌:
IORESOURCE_MEM:內存資源,例如寄存器地址
IORESOURCE_IRQ: IO資源,例如中斷號,GPIO編號
};
數據結構作用:描述硬件外設的純硬件信息
成員:
name:硬件節點的名稱,相當重要,用於匹配
id:硬件節點的編號,如果只有一個硬件信息,一般賦值給-1,如果有多個同名的硬件信息,通過id來區分:0,1,2,3,...
dev:其中利用platform_data來裝載驅動開發者自定義的硬件信息(struct led_resource, struct btn_resource,將自己聲明的描述硬件信息添加到platform_data即可)
resource:指向resource類型的硬件信息
num_resourcs:resource類型的硬件信息的個數
注意:驅動開發者將來用resource來描述硬件還是用自定義的數據結構來描述硬件信息都可以,可以單獨使用,也可以同時使用;
註冊方法:
platform_device_register
卸載方法:
platform_device_unregister
案例
利用platform_device描述硬件信息
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
//聲明描述LED硬件相關的數據結構(自定義)
struct led_resource {
unsigned long phys_addr; //保存起始物理地址
int size; //物理內存大小
int pin; //GPIO編號
};
//定義初始化自定義的LED硬件信息
static struct led_resource led_info = {
.phys_addr = 0xE0200060,
.size = 8,
.pin = 3 //GPC0_3
};
//定義初始化resource類型的硬件信息(resource描述)
static struct resource led_res[] = {
//描述寄存器地址信息
[0] = {
.start = 0xE0200060, //寄存器起始地址
.end = 0xE0200060 + 8, //寄存器結束地址
.flags = IORESOURCE_MEM //標記爲內存資源
},
//描述GPIO編號信息
[1] = {
.start = 3, //GPC0_3
.end = 3, //GPC0_3
.flags = IORESOURCE_IRQ //標記爲IO資源
}
};
static void led_release(struct device *dev) {}
//定義初始化描述LED硬件節點
static struct platform_device led_dev = {
.name = "myled", //用於匹配
.id = -1, //硬件節點編號
.dev = {
.release = led_release, //解除警告
.platform_data = &led_info //裝載自定義的硬件信息
},
.resource = led_res, //裝載resource類型的硬件信息
.num_resources = ARRAY_SIZE(led_res) //resource類型的硬件個數
};
static int led_dev_init(void)
{
//註冊硬件節點到總線的dev鏈表
//並且內核幫你遍歷drv鏈表,進行匹配
//如果匹配成功,內核調用軟件節點的probe函數
//把led_dev首地址傳遞給probe函數
platform_device_register(&led_dev);
printk("led_dev = %#x\n", &led_dev);
return 0;
}
static void led_dev_exit(void)
{
//從dev鏈表卸載硬件節點
//調用軟件節點的remove函數,remove的形參也執行led_dev
platform_device_unregister(&led_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
1.5.struct platform_driver {int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);
struct device_driver driver;
};
struct device_driver {
char *name; //用於匹配
};
說明:描述軟件信息
成員:
probe:軟件和硬件匹配成功,內核調用,pdev指向匹配成功的硬件信息
remove:刪除軟件或者硬件,內核調用,pdev指向匹配成功的硬件信息
driver:其中的name用於匹配
註冊方法:
platform_driver_register
卸載方法:
platform_driver_unregister
注意:probe和remove做什麼事,完全由驅動開發者來決定;
總結:probe一般做三件事:
1.通過pdev指針獲取硬件信息
有自定義的硬件信息
有resource類型的硬件信息
2.處理硬件信息
地址映射
申請GPIO資源
註冊中斷
配置GPIO爲輸出等
3.註冊硬件操作方法
註冊一個字符設備
註冊一個混雜設備
remove是probe函數的死對頭!
案例:利用platform_driver描述軟件信息
實驗步驟:
1.insmod led_dev.ko //先添加硬件
2.insmod led_drv.ko //在添加軟件
3.rmmod led_dev //先卸載硬件
4.rmmod led_drv //在卸載軟件
5.insmod led_drv.ko //先添加軟件
6.insmod led_dev.ko //再添加硬件
7.rmmod led_drv //先卸載軟件
8.rmmod led_dev //再卸載硬件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/io.h>
#include <linux/uaccess.h>
//定義LED開關命令
#define LED_ON 0x100001
#define LED_OFF 0x100002
struct led_resource {
unsigned long phys_addr;
int size;
int pin
};
static unsigned long *gpiocon, *gpiodata;
static int g_pin;
static long led_ioctl(struct file *file,
unsigned int cmd,
unsigned long arg)
{
//分配內核緩衝
int kindex;
//拷貝用戶數據到內核
copy_from_user(&kindex, (int *)arg, sizeof(kindex));
//解析命令
switch(cmd) {
case LED_ON:
if (kindex == 1)
*gpiodata |= (1 << g_pin);
break;
case LED_OFF:
if (kindex == 1)
*gpiodata &= ~(1 << g_pin);
break;
}
printk("%s:配置寄存器=%#x, 數據寄存器=%#x\n",
__func__, *gpiocon, *gpiodata);
return 0;
}
//定義初始化硬件操作方法
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = led_ioctl
};
//定義初始化混雜設備對象
static struct miscdevice led_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "myled",
.fops = &led_fops
};
//匹配成功調用,pdev指向匹配成功的硬件節點(led_dev)
static int led_probe(struct platform_device *pdev)
{
//1.獲取自定義的硬件信息
//pdata指向led_dev.c的led_info
struct led_resource *pdata =
pdev->dev.platform_data;
//2.處理硬件信息
//將物理地址映射到內核空間
gpiocon = ioremap(pdata->phys_addr, pdata->size);
gpiodata = gpiocon + 1;
g_pin = pdata->pin;
//配置GPC0_3輸出口,輸出0
*gpiocon &= ~(0xf << (g_pin *4));
*gpiocon |= (1 << (g_pin * 4));
*gpiodata &= ~(1 << g_pin);
//3.註冊混雜設備
misc_register(&led_misc);
return 0; //成功返回0,失敗返回負值
}
//卸載軟件或者硬件調用,pdev指向匹配成功的硬件節點(led_dev)
static int led_remove(struct platform_device *pdev)
{
//1.卸載混雜設備
misc_deregister(&led_misc);
//2.解除映射
iounmap(gpiocon);
return 0; //成功返回0,失敗返回負值
}
//定義初始化軟件節點
static struct platform_driver led_drv = {
.driver = {
.name = "myled", //用於匹配
},
.probe = led_probe,//匹配成功,內核調用
.remove = led_remove //卸載軟件或者硬件,內核調用
};
static int led_drv_init(void)
{
//添加軟件節點到內核
//內核幫你遍歷dev鏈表,進行匹配
//匹配成功,內核調用probe函數,把硬件節點的首地址傳遞probe函數
platform_driver_register(&led_drv);
return 0;
}
static void led_drv_exit(void)
{
//卸載
platform_driver_unregister(&led_drv);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
內核提供了獲取resource描述的硬件信息函數:
struct resource *platform_get_resource(
struct platform_device *dev,
unsigned int type,
unsigned int num)
函數功能:
通過pdev指針(指向匹配成功的硬件節點)獲取resource類型的硬件信息
參數:
dev:就是pdev
type:資源的類型:
IORESOURCE_MEM或者IORESOURCE_IRQ
num:同類硬件資源的偏移量
返回值:返回硬件信息的首地址
例如:
static struct resource led_res[] = {
//描述寄存器地址信息
[0] = {
.start = 0xE0200060, //寄存器起始地址
.end = 0xE0200060 + 8 - 1, //寄存器結束地址
.flags = IORESOURCE_MEM //標記爲內存資源
},
//描述GPIO編號信息
[1] = {
.start = 3, //GPC0_3
.end = 3, //GPC0_3
.flags = IORESOURCE_IRQ //標記爲IO資源
},
//描述寄存器地址信息
[2] = {
.start = 0xE0200090, //寄存器起始地址
.end = 0xE0200090 + 8, //寄存器結束地址
.flags = IORESOURCE_MEM //標記爲內存資源
},
//描述GPIO編號信息
[3] = {
.start = 4, //GPC0_4
.end = 4, //GPC0_4
.flags = IORESOURCE_IRQ //標記爲IO資源
},
};
在probe函數中獲取:
int led_probe(pdev) {
struct resource *preg_res;
struct resource *preg1_res;
struct resource *ppin_res;
struct resource *ppin1_res;
preg_res =
platform_get_resource(pdev, IORESOURCE_MEM, 0);
preg1_res =
platform_get_resource(pdev, IORESOURCE_MEM, 1);
ppin_res =
platform_get_resource(pdev, IORESOURCE_IRQ, 0);
ppin1_res =
platform_get_resource(pdev, IORESOURCE_IRQ, 1);
}