Linux总线之Platform

文章目录

  • 1.platform总线驱动
    • 1.1设备模型的原理
    • 1.2platform总线驱动的原理
    • 1.3platform驱动的API
    • 1.4platform按照名字匹配的实例
    • 1.5驱动端获取设备信息的过程
    • 1.6idtable的匹配方式
      • 1.6.1为什么需要idtable匹配
      • 1.6.2idtable匹配实例
      • 1.6.3MODULE_DEVICE_TABLE宏的功能
    • 1.7设备树匹配方式
      • 1.7.1设备树中的设备信息的填写
      • 1.7.2在驱动中填写设备树的匹配方式
      • 1.7.3设备树匹配的实例
    • 1.8platform总线驱动的练习

1.platform总线驱动

1.1设备模型的原理

在Linux内核中所有总线驱动都遵从设备驱动的模型,总线驱动的模型如下图:
Linux总线之Platform_第1张图片

内核在设计这些总线驱动模型的时候将一个驱动分为了三个部分device、bus、driver。

device是用来描述硬件设备的,bus是总线用来链接device和driver,driver是用来描述

驱动的对象。在内核中所有的device放在内核的klist_devices的链表中管理,而内核中

所有的driver放在klist_driver中管理。内核中的device和driver通过bus完成关联。

当device和driver匹配成功之后执行驱动的probe函数,在probe函数中就可以完成

操作硬件了。当卸载任何一方驱动的时候都会执行驱动中的remove函数。

1.2platform总线驱动的原理

platform总线驱动遵从设备模型,platform是Linux内核抽象出来的软件代码,并没

有真实的总线协议与之对应。platform总线驱动的思想就是要将设备信息和设备驱动

进行分离。platform_device和platform_driver通过总线匹配成功之后会执行驱动中

probe函数,在probe函数中驱动就能够拿到设备信息。
Linux总线之Platform_第2张图片

1.3platform驱动的API

设备端:
1.分配并初始化对象
    struct platform_device {
        const char	*name;  //用于匹配的名字
        int		id;         //总线号 PLATFORM_DEVID_AUTO 自动分配总线号
        struct device	dev; //父类
        u32		num_resources; //资源的个数
        struct resource	*resource; //资源的首地址
    };

	struct device {  //父类
        void	(*release)(struct device *dev); //释放资源
    }

	struct resource { //设备信息结构体 
        resource_size_t start;  //资源起始值  0x50006000      0xc0008000          71
        resource_size_t end;    //资源的结束值 0x50006000+3   0xc0008000+30       71
        unsigned long flags;    //资源的类型  IORESOURCE_IO IORESOURCE_MEM IORESOURCE_IRQ
    };

2.注册、注销
    platform_device_register(struct platform_device *);  //注册
	platform_device_unregister(struct platform_device *); //注销


驱动端:
1.分配并初始化对象
    struct platform_driver {
        int (*probe)(struct platform_device *);
        //匹配成功执行的函数
        int (*remove)(struct platform_device *);
        //分离的时候执行的函数
        struct device_driver driver;
        //父类
        const struct platform_device_id *id_table;
        //2.idtable匹配
    };
	
	struct device_driver {
        	const char		*name; //1.名字匹配
        	const struct of_device_id	*of_match_table; //3.设备树匹配
    }

2.注册、注销
    platform_driver_register(drv); //注册
    platform_driver_unregister(struct platform_driver *);//注销
3.驱动中一键注册注销的宏
    module_platform_driver(__platform_driver) 
    
   module_driver(__platform_driver, platform_driver_register,platform_driver_unregister)

    #define module_driver(pdrv, __register, __unregister, ...) 

    static int __init pdrv_init(void) 
    { 
        return platform_driver_register(&pdrv ); 
    } 
    module_init(pdrv_init); 
    static void __exit pdrv_exit(void) 
    { 
        platform_driver_unregister(&pdrv); 
    } 
    module_exit(pdrv_exit);

1.4platform按照名字匹配的实例

pdev.c

#include 
#include 
#include 
struct resource res[] = {
    [0] = {
        .start = 0x12345678,
        .end = 0x12345678+49,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = 71,
        .end = 71,
        .flags = IORESOURCE_IRQ,
    },
};
void pdev_release(struct device *dev)
{
    printk("%s:%d\n",__func__,__LINE__);
}
struct platform_device pdev = {
    .name = "hahahaha",
    .id   = PLATFORM_DEVID_AUTO,
    .dev   = {
        .release = pdev_release,
    },
    .num_resources = ARRAY_SIZE(res),
    .resource = res,
};
static int __init pdev_init(void)
{
    return platform_device_register(&pdev);
}
static void __exit pdev_exit(void)
{
    platform_device_unregister(&pdev);
}
module_init(pdev_init);
module_exit(pdev_exit);
MODULE_LICENSE("GPL");

pdrv.c

#include 
#include 
#include 

int pdrv_probe(struct platform_device* pdev)
{
    printk("%s:%d\n", __func__, __LINE__);
    return 0;
}
int pdrv_remove(struct platform_device* pdev)
{
    printk("%s:%d\n", __func__, __LINE__);
    return 0;
}
struct platform_driver pdrv = {
    .probe = pdrv_probe,
    .remove = pdrv_remove,
    .driver = {
        .name = "hahahaha",
    },
};
module_platform_driver(pdrv);
MODULE_LICENSE("GPL");

1.5驱动端获取设备信息的过程

struct resource *platform_get_resource(struct platform_device *dev,
				       unsigned int type, unsigned int index)
功能:在设备驱动中获取设备信息的函数
参数:
    @dev:设备端的结构体指针
	@type:资源的类型
	@index:同类型资源的编号
返回值:成功返回结构体指针,失败返回NULL
        
int platform_get_irq(struct platform_device *dev, unsigned int index)
功能:获取中断类型的资源
参数:
    @dev:设备端的结构体指针
	@index:中断类型资源的编号   
返回值:成功返回中断号,失败返回错误码 

pdrv.c

#include 
#include 
#include 
struct resource *res;
int irqno;
int pdrv_probe(struct platform_device* pdev)
{
    printk("%s:%d\n", __func__, __LINE__);
    //获取IORESOURCE_MEM类型的资源
    res = platform_get_resource(pdev,IORESOURCE_MEM,0);
    if(res == NULL){
        printk("platform get resource error\n");
        return -ENODATA;
    }
    //获取中断类型的资源
    irqno = platform_get_irq(pdev,0);
    if(irqno < 0){
        printk("get irq resource error\n");
        return irqno;
    }
    printk("addr = %#llx,irqno = %d\n",res->start,irqno);
    return 0;
}
int pdrv_remove(struct platform_device* pdev)
{
    printk("%s:%d\n", __func__, __LINE__);
    return 0;
}
struct platform_driver pdrv = {
    .probe = pdrv_probe,
    .remove = pdrv_remove,
    .driver = {
        .name = "hahahaha",
    },
};
module_platform_driver(pdrv);
MODULE_LICENSE("GPL");

1.6idtable的匹配方式

1.6.1为什么需要idtable匹配

内核中的驱动有的时候需要支持一系列设备,这一系列设备的名就可以放在idtable中。

不管用户使用的那个设备只要和idtable中的名字匹配成功都需要执行驱动中的probe函数。

如果驱动中写了idtable和name.先按照idtable匹配,如果匹配不上在按照name匹配。

name可以不用于匹配,但必须填充。

struct platform_device_id {
	char name[PLATFORM_NAME_SIZE]; //名字
	kernel_ulong_t driver_data;   //给驱动传递的数据
};

struct platform_device_id idtable[] = {
    {"hello0",0},
    {"hello1",1},
    {"hello2",2},
    {"hello3",3},
    {},           //如果前面的成员都没有匹配上从这里退出
};
struct platform_driver pdrv = {
    .probe = pdrv_probe,
    .remove = pdrv_remove,
    .driver = {
        .name = "hahahaha",
    },
    .id_table = idtable,
};

1.6.2idtable匹配实例

#include 
#include 
#include 
#include 
struct resource *res, *res1;
int irqno;
int pdrv_probe(struct platform_device* pdev)
{
    printk("%s:%d\n", __func__, __LINE__);
    printk("data = %d\n",pdev->id_entry->driver_data);
    
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (res == NULL) {
        printk("platform get resource error\n");
        return -ENODATA;
    }
    res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    if (res1 == NULL) {
        printk("platform get resource error\n");
        return -ENODATA;
    }
    irqno = platform_get_irq(pdev, 0);
    if (irqno < 0) {
        printk("get irq resource error\n");
        return irqno;
    }

    printk("addr = %#llx,addr1=%#llx,irqno = %d\n", res->start,res1->start,irqno);
    
    return 0;
}
int pdrv_remove(struct platform_device* pdev)
{
    printk("%s:%d\n", __func__, __LINE__);
    return 0;
}
struct platform_device_id idtable[] = {
    {"hello0",0},
    {"hello1",1},
    {"hello2",2},
    {"hello3",3},
    {},           //如果前面的成员都没有匹配上从这里退出
};
struct platform_driver pdrv = {
    .probe = pdrv_probe,
    .remove = pdrv_remove,
    .driver = {
        .name = "hahahaha",
    },
    .id_table = idtable,
};
module_platform_driver(pdrv);
MODULE_LICENSE("GPL");

1.6.3MODULE_DEVICE_TABLE宏的功能

MODULE_DEVICE_TABLE(总线类型, idtable数组名);

完成热插拔效果的宏

效果演示:

1.将pdev.ko 和pdrv.ko放到如下目录

/lib/modules/5.4.0-104-generic/kernel/drivers/platform

2.让系统重新扫描目录

sudo depmod -a

3.重启ubuntu系统

sudo reboot

4.安装pdev.ko,pdrv.ko会被自动安装并执行
Linux总线之Platform_第3张图片

1.7设备树匹配方式

在带设备树的内核版本中platform_device描述的设备信息都被放到了设备树中。

所以platform_device的驱动就不用写了。如果使用platform的设备树的匹配的方式,

只需要填写设备树,并编写platform_driver的代码即可。

1.7.1设备树中的设备信息的填写

myplatform{
    compatible = "hqyj,myplatform"; //<====必须填充
    reg = <0x12345678 0x31>;
    interrupt-parent = <&gpiof>;
    interrupts = <9 0>;
};

1.7.2在驱动中填写设备树的匹配方式

struct of_device_id {
	char	name[32];
	char	type[32];
	char	compatible[128];  //一般选择这种匹配方式
	const void *data;
};

struct of_device_id oftable[] = {
    {.compatible = "hqyj,myplatform",},
    {}  //不能省略
};
MODULE_DEVICE_TABLE(of, oftable);

struct platform_driver pdrv = {
    .probe = pdrv_probe,
    .remove = pdrv_remove,
    .driver = {
        .name = "hahahaha",
        .of_match_table = oftable,
    },
};

1.7.3设备树匹配的实例

#include 
#include 
#include 
#include 
#include 
// myplatform{
//     compatible = "hqyj,myplatform"; //<====必须填充
//     reg = <0x12345678 0x31>;
//     interrupt-parent = <&gpiof>;
//     interrupts = <9 0>;
//     led1 = <&gpioe 10 0>; //自己的键值对
// };
struct resource *res;
int irqno;
int pdrv_probe(struct platform_device* pdev)
{
    printk("%s:%d\n", __func__, __LINE__);
    //解析自己的属性
    //gpiod_get_from_of_node(pdev->dev.of_node,"led1",0,GPIOD_OUT_LOW,NULL);
    
    //IORESOURCE_MEM获取设备树中的reg
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (res == NULL) {
        printk("platform get resource error\n");
        return -ENODATA;
    }
    //获取设备树中的中断号
    irqno = platform_get_irq(pdev, 0);
    if (irqno < 0) {
        printk("get irq resource error\n");
        return irqno;
    }

    printk("addr = %#x,irqno = %d\n", res->start,irqno);
    
    return 0;
}
int pdrv_remove(struct platform_device* pdev)
{
    printk("%s:%d\n", __func__, __LINE__);
    return 0;
}

struct of_device_id oftable[] = {
    {.compatible = "hqyj,myplatform",},
    {}  //不能省略
};
MODULE_DEVICE_TABLE(of, oftable);

struct platform_driver pdrv = {
    .probe = pdrv_probe,
    .remove = pdrv_remove,
    .driver = {
        .name = "hahahaha",
        .of_match_table = oftable,
    },
};
module_platform_driver(pdrv);
MODULE_LICENSE("GPL");

1.8platform总线驱动的练习

1.基于platform驱动实现如下要求                   //platform总线驱动
a.应用程序通过阻塞的io模型来读取status变量的值  //等待队列
b.status是内核驱动中的一个变量,代表LED1的状态
c.status的值随着按键按下而改变(按键中断)      //按键中断
	例如status=0 按下按键status=1 ,再次按下按键status=0
d.在按下按键的时候需要同时将led1的状态取反     //gpio子系统
e.驱动中需要编写字符设备驱动                   //字符设备驱动
f.驱动中需要自动创建设备节点                   //自动创建设备节点
g.这个驱动需要的所有设备信息放在设备树的同一个节点中 //设备树

platform_irq_led.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
// myplatform{
//     compatible = "hqyj,myplatform"; //<====必须填充
//     reg = <0x12345678 0x31>;
//     interrupt-parent = <&gpiof>;
//     interrupts = <9 0>;
//     led1 = <&gpioe 10 0>; //自己的键值对
// };
#define CNAME "platform_irq_led"
struct device_node* node;
struct gpio_desc* desc;
unsigned int irqno;
int major;
const int count = 1;
struct class* cls;
struct device* dev;
wait_queue_head_t wq;
int condition = 0;
int status = 0;
irqreturn_t platform_irq_led_handle(int irq, void* dev)
{
    //取反灯的状态
    status = gpiod_get_value(desc);
    status = !status;
    gpiod_set_value(desc, status);
    //唤醒阻塞
    condition = 1;
    wake_up_interruptible(&wq);
    return IRQ_HANDLED;
}
int gpio_irq_init(struct device_node* node)
{
    int ret;
    //获取并初始化gpio
    desc = gpiod_get_from_of_node(node, "led1", 0, GPIOD_OUT_LOW, NULL);
    if (IS_ERR(desc)) {
        printk("get gpio error\n");
        ret = PTR_ERR(desc);
        goto ERR1;
    }
    //获取并注册中断
    // irqno = platform_get_irq(pdev, 0);
    irqno = irq_of_parse_and_map(node, 0);
    if (irqno == 0) {
        printk("parse irqno error\n");
        ret = -EINVAL;
        goto ERR2;
    }
    ret = request_irq(irqno, platform_irq_led_handle,
        IRQF_TRIGGER_FALLING, CNAME, NULL);
    if (ret) {
        printk("request irq error\n");
        goto ERR2;
    }

    return 0;
ERR2:
    gpiod_put(desc);
ERR1:
    return ret;
}
void gpio_irq_deinit(void)
{
    free_irq(irqno, NULL);
    gpiod_put(desc);
}
int platform_irq_led_open(struct inode* inode, struct file* file)
{
    return 0;
}
ssize_t platform_irq_led_read(struct file* filp,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    // 1.判断用户是否是阻塞打开
    if (filp->f_flags & O_NONBLOCK) {
        return -EINVAL;
    } else {
        // 2.阻塞
        ret = wait_event_interruptible(wq, condition);
        if (ret) {
            printk("receive signal....\n");
            return ret;
        }
    }
    // 3.将灯的状态返回到用户空间
    if (size > sizeof(status))
        size = sizeof(status);
    ret = copy_to_user(ubuf, &status, size);
    if (ret) {
        printk("copy data to user error\n");
        return -EIO;
    }
    //4.条件清零
    condition = 0;

    return size;
}
int platform_irq_led_close(struct inode* inode, struct file* file)
{
    return 0;
}

static struct file_operations fops = {
    .open = platform_irq_led_open,
    .read = platform_irq_led_read,
    .release = platform_irq_led_close,
};

int platform_irq_led_probe(struct platform_device* pdev)
{
    int ret;
    printk("%s:%d\n", __func__, __LINE__);
    // 1.初始化led和irq
    if ((ret = gpio_irq_init(pdev->dev.of_node)) != 0)
        return ret;

    // 2.注册字符设备驱动
    major = register_chrdev(0, CNAME, &fops);
    if (major < 0) {
        printk("register chrdev error\n");
        ret = -EAGAIN;
        goto ERR1;
    }
    // 3.创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class create error\n");
        ret = PTR_ERR(cls);
        goto ERR2;
    }
    dev = device_create(cls, NULL, MKDEV(major, 0), NULL, CNAME);
    if (IS_ERR(dev)) {
        printk("device create error\n");
        ret = PTR_ERR(dev);
        goto ERR3;
    }
    //初始化等待队列头
    init_waitqueue_head(&wq);
    return 0;
ERR3:
    class_destroy(cls);
ERR2:
    unregister_chrdev(major, CNAME);
ERR1:
    gpio_irq_deinit();
    return ret;

    return 0;
}
int platform_irq_led_remove(struct platform_device* pdev)
{
    printk("%s:%d\n", __func__, __LINE__);
    device_destroy(cls, MKDEV(major, 0));
    class_destroy(cls);
    unregister_chrdev(major, CNAME);
    gpio_irq_deinit();
    return 0;
}

struct of_device_id oftable[] = {
    {
        .compatible = "hqyj,myplatform",
    },
    {} //不能省略
};
MODULE_DEVICE_TABLE(of, oftable);

struct platform_driver platform_irq_led = {
    .probe = platform_irq_led_probe,
    .remove = platform_irq_led_remove,
    .driver = {
        .name = "hahahaha",
        .of_match_table = oftable,
    },
};
module_platform_driver(platform_irq_led);
MODULE_LICENSE("GPL");

test.c

#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	int fd,status;
	if((fd = open("/dev/platform_irq_led",O_RDWR)) < 0){
		perror("open error");
		exit(EXIT_FAILURE);
	}
	while(1){
		read(fd,&status,sizeof(status));
		printf("status = %d\n",status);
	}

	close(fd);
	return 0;
}

你可能感兴趣的:(linux,运维,服务器)