宋宝华:Linux设备与驱动的手动解绑与手动绑定

众所周知,Linux靠设备与驱动之间的match,来完成设备与驱动的bind,从而触发驱动的probe()成员函数被执行。每个bus都有相应的match方法,完成match的总的入口函数是:

static inline int driver_match_device(struct device_driver *drv,
                                      struct device *dev)
{
        return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

而这个总的入口函数又会调用到各自不同总线的match函数,对于platform bus而言,它的match函数就是platform_match()

static int platform_match(struct device *dev, struct device_driver *drv)
{
        struct platform_device *pdev = to_platform_device(dev);
        struct platform_driver *pdrv = to_platform_driver(drv);


        /* When driver_override is set, only bind to the matching driver */
        if (pdev->driver_override)
                return !strcmp(pdev->driver_override, drv->name);


        /* Attempt an OF style match first */
        if (of_driver_match_device(dev, drv))
                return 1;


        /* Then try ACPI style match */
        if (acpi_driver_match_device(dev, drv))
                return 1;


        /* Then try to match against the id table */
        if (pdrv->id_table)
                return platform_match_id(pdrv->id_table, pdev) != NULL;


        /* fall-back to driver name match */
        return (strcmp(pdev->name, drv->name) == 0);
}


从代码可以看出,platform的driver和device之间的match有很多方法成立,比如设备的name和驱动的name相同:

strcmp(pdev->name, drv->name) == 0

比如,设备的名字出现在驱动的ID表中:

        if (pdrv->id_table)
                return platform_match_id(pdrv->id_table, pdev) != NULL;

比如device tree里面的compatible字段与驱动的dt兼容性字段匹配:

        if (of_driver_match_device(dev, drv))
                return 1;

只要符合其中任意一种,driver和device都可以匹配上。

这种自动匹配非常简单,实施起来也非常容易。

但是有时候,这种自动匹配并不一定是我们想要的。比如我们有时候就是希望XXX设备用YYY驱动,而不是用XXX驱动。工程中有手动匹配的需求,最典型的场景是VFIO的场景,想让设备与内核空间原本绑定的驱动解绑,转而采用内核空间的通用VFIO驱动,而VFIO驱动又提供了userspace驾驭设备的能力。

下面我们来从原理和实践上演示这种手动的unbind和bind是怎么进行的。在《Linux设备驱动开发详解》一书中,我们给出了一个简单的globalfifo设备和globalfifo驱动:

globalfifo-dev.ko(增加platform_device的模块):

static int __init globalfifodev_init(void)
{
  int ret;


  globalfifo_pdev = platform_device_alloc("globalfifo", -1);


  ret = platform_device_add(globalfifo_pdev);
  ...
  return 0;


}
module_init(globalfifodev_init);

globalfifo.ko(增加platform_driver的模块):

static struct platform_driver globalfifo_driver = {
  .driver = {
    .name = "globalfifo",
    .owner = THIS_MODULE,
  },
  .probe = globalfifo_probe,
  .remove = globalfifo_remove,
};


module_platform_driver(globalfifo_driver);

由于其中的platform_driver和platform_device的name都是“globalfifo”,符合此行的匹配规则:

strcmp(pdev->name, drv->name) == 0

设备和驱动匹配成功,从sysfs也可以看出:

globalfifo的device和driver各自找到了对方。

现在我们来写一个第三者driver,名字叫做globalxxx,然后我们想把globalfifo device的driver指向globalxxx。因此我们要完成2步:

  1. unbind:解除globalfifo driver与globalfifo device的绑定

  2. bind: 进行globalxxx driver与globalfifo device的绑定

第三者globalxxx驱动代码类似:

globalxxx.ko(增加platform_driver的模块):

static struct platform_driver globalxxx_driver = {
  .driver = {
    .name = "globalxxx",
    .owner = THIS_MODULE,
  },
  .probe = globalxxx_probe,
  .remove = globalxxx_remove,
};


module_platform_driver(globalxxx_driver);

下面我们来完成第一步的unbind,这一步很简单,跑到/sys/bus/platform/drivers/globalfifo目录,把设备globalfifo的名字写进去unbind文件:

当然我们也可以来回折腾着unbind,bind着玩:

这样我们看到一堆的probe(每次设备和驱动bind成功,驱动probe都会执行),remove(每次设备和驱动unbind成功,驱动remove都会执行),最后处于unbind状态。

现在我们来把globalfifo设备bind到globalxxx驱动:

绑定的时候提示错误!

绑定的时候提示错误!!

绑定的时候提示错误!!!

前面我们用globalfifo的driver去bind globalfifo的device的时候,是想怎么绑就怎么绑的,想绑多少次就绑多少次的!为什么换了globalxxx来绑就不行了呢?

爱情不是你想卖想买就能卖

让我挣开 让我明白

放手你的爱

我们来看看这个bind sysfs入口工作的函数bind_store():

static ssize_t bind_store(struct device_driver *drv, const char *buf,
                          size_t count)
{
        ...
        dev = bus_find_device_by_name(bus, NULL, buf);
        if (dev && dev->driver == NULL && driver_match_device(drv, dev)) {
                err = device_driver_attach(drv, dev);


                if (err > 0) { 
                        /* success */
                        err = count;
                } else if (err == 0) { 
                        /* driver didn't accept device */
                        err = -ENODEV;
                }    
        }    
        ...
}

看起来,如果要强行bind,仍然需要device_driver_attach()成立,否则内核会返回-ENODEV错误:

                } else if (err == 0) { 
                        /* driver didn't accept device */
                        err = -ENODEV;
                }

根据前文对platform_match()的代码分析,globalxxx driver和globalfifo device确实八竿子都打不着!!没有任何匹配因子。

下面我们来把globalxxx的代码稍微改一下,通过ID表来增加一个匹配因子:

static const struct platform_device_id globalxxx_ids[] = {
        {
                .name = "globalfifo",
        },
        {}
};
MODULE_DEVICE_TABLE(platform, globalxxx_ids);


static struct platform_driver globalxxx_driver = {
  .driver = {
    .name = "globalxxx",
    .owner = THIS_MODULE,
  },
  .id_table = globalxxx_ids,
  .probe = globalxxx_probe,
  .remove = globalxxx_remove,
};


module_platform_driver(globalxxx_driver);

rmmod和insmod globalxxx.ko

然后重新bind:

现在globalfifo device可以在globalxxx和globalfifo这2个driver里面进行自由地bind和unbind!

看到这里,客官们一定觉得这太特么狗血了!不是说可以自由地绑定第三者吗?为嘛还要求这个第三者驱动与这个原先的设备匹配呢?这有嘛意思呢?

别忘了,在platform_match中还有这么一行:

        if (pdev->driver_override)
                return !strcmp(pdev->driver_override, drv->name);

设备完全可以自由地宣布她喜欢的第三者driver,哪怕这个第三者driver和她本身完全没有任何的匹配因子,操作的入口就是driver_override sysfs文件。

我们完全可以保留globalxxx驱动的原样

static struct platform_driver globalxxx_driver = {
  .driver = {
    .name = "globalxxx",
    .owner = THIS_MODULE,
  },
  .probe = globalxxx_probe,
  .remove = globalxxx_remove,
};

不去增加任何的id_table,而换做到globalfifo device里面去写driver_override文件,宣布globalxxx driver可以匹配globalfifo device。

这样之后,哪怕globalxxx driver和globalfifo device八竿子打不着,也是可以驱动globalfifo device的。工程里面如果我们想用VFIO的方式来驱动一个设备,就可以这样做:

echo vfio-platform > \
    driver_override

(END)

Linux阅码场原创精华文章汇总

更多精彩,尽在"Linux阅码场",扫描下方二维码关注

别忘了点个赞和在看哦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宋宝华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值