笔者认为,platform最大的好处有两点:一是驱动分离,二是更加简单,采用了自动匹配的方法,不用搜索设备树去找节点。
实际上,platfrom由三部分组成:bus、driver、device。一般而言(也是力求最简单的情况下),bus已经被linux自己写好了,device用设备树描述。所以驱动开发者只需要对driver进行操作即可。而这个driver和普通的驱动的套路相差不大。
drvier用一个叫platform_driver 的结构体表示,用到platform的话就要自己声明一个结构体。
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
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;
bool prevent_deferred_probe;
};
基本上,我们最关心其中三个成员,写platform驱动只需要自己实现这个三个成员变量就可以了。有特殊需求再搞其他的成员。
自己的platform_driver的实现好了,就要向内核进行注册:
int platform_driver_register (struct platform_driver *driver)
函数参数和返回值含义如下:
driver:要注册的 platform 驱动。
返回值:负数,失败;0,成功。
还需要在驱动卸载函数中通过 platform_driver_unregister 函数卸载 platform 驱动,
void platform_driver_unregister(struct platform_driver *drv)
drv:要卸载的 platform 驱动。
这两个函数可以在原来的init,exit中调用。也就是说init,exit函数只需要分别干注册和注销一件事。这不是绕吗。所以还有更简单的方法:
void module_platform_driver(struct platform_driver *driver)
在实际操作中,只需要写这么一句就完成了对设备的注册,相当方便。
在哪写呢?原来在哪写init、exit,现在就在哪写,不是它们的里面哦。
有了platform以后,就不需要用以前的各种OF函数找设备节点,获取相关信息了。我们现在有了新的API。
要获取什么信息呢?无外乎就是一些编号,中断号之类的。对于我现在的ZYNQ平台下的自定义IP,还要获取操作寄存器的地址。
什么时候获取信息呢?肯定是要在初始化的时候获取信息呀。什么时候初始化呢?probe函数中呀。probe函数传递进来个参数:*struct device dev。只要利用这个参数还有一系列API就可以方便实现的获取设备树种的各种信息。
那有什么API函数呢?linux/property.h定义了一堆。
https://blog.csdn.net/jklinux/article/details/78575281
这里写得比较详细了,还给了个例子。
笔者最常用:
int device_property_read_u32_array(struct device *dev, const char *propname, u32 *val, size_t nval);
是不是OF函数很像?
linux/platform_device.h里面也有几个可以用:
int platform_get_irq(struct platform_device *dev, unsigned int index);
这个就是为了获取设备节点的中断编号并进行映射的API了。
拿到信息之后,省下的就是老一套了。例如申请中断什么的,和之前的都没有变化。
1.肯定要先创建一个设备树的节点。最中要的肯定是compatible属性,要不然没法匹配了。
breath_led_ip_0: breath_led_ip@43c20000 {
clock-names = "s0_axi_aclk";
clocks = <&clkc 15>;
compatible = "xlnx,breath-led-ip-1.0"; //就是它!
interrupt-names = "breath_intr";
interrupt-parent = <&intc>;
interrupts = <0 29 4>; //
reg = <0x43c20000 0x10000>; //寄存器首地址 和 深度
xlnx,s0-axi-addr-width = <0x4>;
xlnx,s0-axi-data-width = <0x20>;
};
笔者用的是ZYNQ平台,这是笔者自己做的一个测试IP的设备树,上面的compatible属性就是要和driver匹配的属性值了。都是自动生成的,具体可以参考:
https://blog.csdn.net/qq_26911733/article/details/105748629
static const struct of_device_id ip_of_match[] = {
{.compatible = "xlnx,breath-led-ip-1.0" }, //要和设备数的要匹配的节点一毛一样
{ }
};
MODULE_DEVICE_TABLE(of, ip_of_match); //不要问为什么,问就是套路
static struct platform_driver ip_driver = {
.probe = ip_probe,
.remove = ip_remove,
.driver = {
.owner = THIS_MODULE,
.name = "breath_ip", //这里的名字本来是为了非设备树的情况进行匹配,现在理论上不需要,可是会报错,还是加上一个吧
.of_match_table = ip_of_match, //就是上面的of_match_table啦
},
};
static int ip_probe(struct platform_device *pdev){
//自己的初始化代码,原来的init
}
static int ip_remove(struct platform_device *pdev){
//自己的驱动卸载代码,原来的exit
}
module_platform_driver(ip_driver);
// module_init(xxx_init); //现在不用了
// module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("intr_ip_test");
大功告成。都是套路。
ZYNQ自定了一个IP,里面会每隔一段时间产生一个中断信号。驱动接收信号,标志数据准备好,利用异步IO发送给应用层读取。应用层读取后,驱动标志数据没准备好,以此反复。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define NEWCHRLED_CNT 1 //the number of the devids
#define NEWCHRLED_NAME "intr_ip_test"
static void __iomem *LED_CTR_REG_MAP;
static void __iomem *LED_FRE_REG_MAP;
struct intr_ip_test_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
spinlock_t lock;
struct device_node *nd;
unsigned int irq; //the interrupt number of breath_led_ip
unsigned int regdata[2]; //the two control registers made by ourselves
int val; //the interrupt test flag(data ready flag)
struct tasklet_struct testtasklet;
struct fasync_struct *ip_fasync;
};
static struct intr_ip_test_dev intr_ip_test;
void inline open_led(void){
writel(1,LED_CTR_REG_MAP);
}
void inline close_led(void){
writel(0,LED_CTR_REG_MAP);
}
void inline set_led_g1(void){
writel(0x8000002f,LED_FRE_REG_MAP);
}
void inline set_led_g2(void){
writel(0x800000ef,LED_FRE_REG_MAP);
}
void inline reg_map (void){
LED_CTR_REG_MAP = ioremap(intr_ip_test.regdata[0], 4);
LED_FRE_REG_MAP = ioremap(intr_ip_test.regdata[0]+4, 4);
}
void inline reg_unmap (void){
iounmap(LED_CTR_REG_MAP);
iounmap(LED_FRE_REG_MAP);
}
static irqreturn_t ip_intr_handler(int irq, void *dev){
//printk("intr detected!\r\n");
/* awaken the tasklet function*/
tasklet_schedule(&intr_ip_test.testtasklet);
return IRQ_RETVAL(IRQ_HANDLED);
}
/* tasklet function */
void testtasklet_func(unsigned long data){
intr_ip_test.val = 1;
// this val will be cleared as the application APP reads the data from the driver.
kill_fasync (&intr_ip_test.ip_fasync, SIGIO, POLL_IN);
}
static int chrdev_open(struct inode *inode, struct file *filp)
{
filp->private_data = &intr_ip_test; /* set our own structure as the private_data */
printk("intr_ip_test open\r\n");
return 0;
}
static ssize_t chrdev_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&intr_ip_test.lock,flags);
ret = copy_to_user(buf,&intr_ip_test.val,sizeof(intr_ip_test.val));
intr_ip_test.val = 0; //clear val
spin_unlock_irqrestore(&intr_ip_test.lock,flags);
return 0;
}
static ssize_t chrdev_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t chrdev_fasync (int fd,struct file *filp, int on)
{
return fasync_helper(fd, filp, on, &intr_ip_test.ip_fasync);
}
static int chrdev_release(struct inode *inode, struct file *filp)
{
printk("intr_ip_test release!\r\n");
return chrdev_fasync(-1, filp, 0);
}
static struct file_operations chrdev_fops = {
.owner = THIS_MODULE,
.open = chrdev_open,
.read = chrdev_read,
.write = chrdev_write,
.release = chrdev_release,
.fasync = chrdev_fasync,
};
static int ip_probe(struct platform_device *pdev)
{
int ret;
spin_lock_init(&intr_ip_test.lock);
/* the initialization of the tasklet*/
tasklet_init(&intr_ip_test.testtasklet, testtasklet_func, NULL);
/* 现在原来的一套东西就不需要了 */
// intr_ip_test.nd = of_find_node_by_path("/amba_pl/breath_led_ip@43c20000");
// if(intr_ip_test.nd == NULL){
// printk("no node from dts found!\r\n");
// goto out0;
// }
// ret = of_property_read_u32_array(intr_ip_test.nd, "reg", intr_ip_test.regdata, 2);
// if(ret < 0){
// printk("reg read failed!\r\n");
// } else {
// u8 i = 0;
// printk("reg data:\r\n");
// for(i = 0; i < 2; i++)
// printk("%#X ", intr_ip_test.regdata[i]);
// printk("\r\n");
// }
/* 换成下面的: */
ret = device_property_read_u32_array(&pdev->dev,"reg",intr_ip_test.regdata,2 );
if(ret < 0){
printk("reg read failed!\r\n");
} else {
u8 i = 0;
printk("reg data:\r\n");
for(i = 0; i < 2; i++)
printk("%#X ", intr_ip_test.regdata[i]);
printk("\r\n");
}
/*ip interrupt*/
intr_ip_test.irq = platform_get_irq(pdev,0);
// intr_ip_test.irq = irq_of_parse_and_map(intr_ip_test.nd,0);
ret = request_irq(intr_ip_test.irq,
ip_intr_handler,
IRQF_TRIGGER_RISING,
"BREATH_LED",
NULL);
if(ret < 0 )
{
printk("irq %d request failed\r\n", intr_ip_test.irq);
ret = -EFAULT;
goto out0;
}
printk("irq %d request done\r\n", intr_ip_test.irq);
/* major minor and devid */
ret = alloc_chrdev_region(&intr_ip_test.devid,0,NEWCHRLED_CNT,NEWCHRLED_NAME);
if(ret)
goto out1;
intr_ip_test.major = MAJOR(intr_ip_test.devid);
intr_ip_test.major = MINOR(intr_ip_test.devid);
printk("intr_ip_test major=%d,minor=%d\r\n",intr_ip_test.major, intr_ip_test.minor);
/*initailization of cdev*/
intr_ip_test.cdev.owner = THIS_MODULE;
cdev_init(&intr_ip_test.cdev,&chrdev_fops);
ret = cdev_add(&intr_ip_test.cdev, intr_ip_test.devid,NEWCHRLED_CNT);
if(ret)
goto out2;
/* create a class*/
intr_ip_test.class = class_create(THIS_MODULE,NEWCHRLED_NAME);
if (IS_ERR(intr_ip_test.class)) {
ret = PTR_ERR(intr_ip_test.class);
goto out3;
}
intr_ip_test.device = device_create(intr_ip_test.class, NULL,
intr_ip_test.devid, NULL, NEWCHRLED_NAME);
if (IS_ERR(intr_ip_test.device)) {
ret = PTR_ERR(intr_ip_test.device);
goto out4;
}
/*map the addresses of the my_ip*/
reg_map();
open_led();
set_led_g1();
return 0;
out4:
class_destroy(intr_ip_test.class);
out3:
cdev_del(&intr_ip_test.cdev);
out2:
unregister_chrdev_region(intr_ip_test.devid,NEWCHRLED_CNT);
out1:
free_irq(intr_ip_test.irq, NULL);
out0:
return ret;
}
static int ip_remove(struct platform_device *pdev)
{
// printk("Closing the driver!");
close_led();
reg_unmap();
disable_irq_nosync(intr_ip_test.irq);
device_destroy(intr_ip_test.class, intr_ip_test.devid);
class_destroy(intr_ip_test.class);
cdev_del(&intr_ip_test.cdev);
unregister_chrdev_region(intr_ip_test.devid,NEWCHRLED_CNT);
free_irq(intr_ip_test.irq, NULL);
return 0;
}
static const struct of_device_id ip_of_match[] = {
{.compatible = "xlnx,breath-led-ip-1.0" },
{ }
};
MODULE_DEVICE_TABLE(of, ip_of_match);
static struct platform_driver ip_driver = {
.probe = ip_probe,
.remove = ip_remove,
.driver = {
.owner = THIS_MODULE,
.name = "breath_ip",
.of_match_table = ip_of_match,
},
};
module_platform_driver(ip_driver);
// module_init(intr_ip_test_init);
// module_exit(intr_ip_test_exit);
MODULE_AUTHOR("zangyujiagood");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("intr_ip_test");
#include "stdlib.h"
#include "string.h"
#include "sys/select.h"
#include "sys/time.h"
#include "linux/ioctl.h"
#include
#include
#include
#include
#include
#include
#include
static int fd = 0;
static void sigio_signal_func(){
int ret = 0;
unsigned int value;
printf("signal received!\r\n");
ret = read(fd,&value,sizeof(value));
if(ret < 0){
printf("read failed\r\n");
}
if(1 == value){
printf("pl interrupt!\r\n");
}
printf("leaving the signal \r\n");
}
int main(int argc, char *argv[])
{
int flags;
/* 判断传参个数是否正确 */
if(2 != argc) {
printf("Usage:\n"
"\t./App_my_ip_intr /dev/intr_ip_test\n"
);
return -1;
}
fd = open(argv[1], O_RDONLY);
if(0 > fd) {
printf("ERROR: %s file open failed!\n", argv[1]);
return -1;
}
signal(SIGIO,sigio_signal_func);
fcntl(fd,F_SETOWN,getpid()); //设置异步I/O所有权
flags = fcntl(fd,F_GETFD);
fcntl(fd, F_SETFL, flags | FASYNC);
/* wait for the signal */
for ( ; ; ) {
sleep(1);
}
close(fd);
return 0;
}