platform与设备树

要掌握什么

笔者认为,platform最大的好处有两点:一是驱动分离,二是更加简单,采用了自动匹配的方法,不用搜索设备树去找节点。
实际上,platfrom由三部分组成:bus、driver、device。一般而言(也是力求最简单的情况下),bus已经被linux自己写好了,device用设备树描述。所以驱动开发者只需要对driver进行操作即可。而这个driver和普通的驱动的套路相差不大。

用户怎么描述platform_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驱动只需要自己实现这个三个成员变量就可以了。有特殊需求再搞其他的成员。

  1. driver成员里面有个of_match_table
    of_match_table里面存放着要和设备树节点匹配的信息,就是和compatible属性匹配的信息,就是一些名字。
    什么时候开始匹配呢?这个时间就是加载driver这个ko文件的时候,linux会将里面存放的信息和设备树compatible属性进行匹配。的一毛一样就是就可以匹配成功了。所以要在它里面设置一个“名字表”,具体要做什么在下一节讲。
  2. probe函数
    匹配成功之后,系统会自动调用probe函数。那我们要在probe函数里做什么呢?显然就是原来init函数里面的东西嘛。只不过要替换成一些platform自己的API就好了~
    3.remove函数
    卸载的东西,就是原来exit的东西,再做一些替换就好了。

用户怎么注册platform_driver

自己的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,现在就在哪写,不是它们的里面哦。

获取设备树信息的API

有了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

  1. 驱动文件里找一个合适的地方添加“匹配列表”,就是上面提到的of_match_table啦:
static const struct of_device_id ip_of_match[] = {
	{.compatible = "xlnx,breath-led-ip-1.0" },  //要和设备数的要匹配的节点一毛一样
	{ }
};
MODULE_DEVICE_TABLE(of, ip_of_match);   //不要问为什么,问就是套路
  1. 找一个合适地方声明platform_driver
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啦
	},
};
  1. 实现probe函数和remove函数
static int ip_probe(struct platform_device *pdev){
	//自己的初始化代码,原来的init
}
static int ip_remove(struct platform_device *pdev){
	//自己的驱动卸载代码,原来的exit
}
  1. 注册一下:
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;
}

你可能感兴趣的:(platform与设备树)