天天看點

i.MX6ULL驅動開發 | 23 - Linux下的驅動分離與分層——platform平台驅動模型

一、Linux驅動的分離

1. 為什麼需要驅動分離?

在嵌入式開發中,無論處理器如何更換,外設子產品的操作都是一緻的,比如有三個不同的平台都要驅動MPU6050傳感器,最簡單的方法是針對每個平台都寫一份驅動:

i.MX6ULL驅動開發 | 23 - Linux下的驅動分離與分層——platform平台驅動模型

顯然這種處理方式太low了,MPU6050都是使用I2C接口操作的,對于不同的平台,隻是I2C操作方式不一樣,是以這裡可以将I2C接口抽象出來,給不同的平台用自己的庫函數适配:

i.MX6ULL驅動開發 | 23 - Linux下的驅動分離與分層——platform平台驅動模型

這樣多種平台就可以共用同一份MPU6050驅動:

i.MX6ULL驅動開發 | 23 - Linux下的驅動分離與分層——platform平台驅動模型

2. Linux核心中的驅動分離

在Linux核心中,一般SOC的主機控制器驅動已經由半導體廠家寫好了,比如這裡imx6ull的i2c控制器驅動已經由NXP寫好了。

而對于具體的裝置,比如MPU6050等裝置,其驅動程式也由裝置廠商寫好了。

我們要做的是提供裝置資訊即可,比如:裝置連接配接到了哪個I2C接口上?支援的速率是多少?裝置在總線上的從機位址是多少?等等。

這樣設計之下,SOC的外設驅動隻負責外設驅動,某個裝置的驅動隻負責裝置驅動,使用時隻需要讓核心将二者聯系起來即可。

i.MX6ULL驅動開發 | 23 - Linux下的驅動分離與分層——platform平台驅動模型

當我們向系統注冊一個驅動的時候,核心就會在右側的裝置中查找有沒有比對的裝置,同樣,當我們向系統中注冊一個裝置的時候,核心就會在左側的驅動中查找有沒有比對的驅動。

這個就是Linux核心中的總線(bus)、驅動(driver)、裝置(device)模型,也稱之為Linux核心中的驅動分離。

3. Linux核心中的驅動分層

分層的目的是為了在不同的層處理不同的内容。

以input子系統為例,input子系統負責管理所有跟輸入有關的驅動,包括鍵盤、滑鼠、觸摸闆等。

  • 裝置原始驅動層:負責擷取輸入裝置的原始值。
  • input核心層:處理各種IO模型,并且提供file_operations操作集合。

這樣分層設計之後,在編寫輸入設别的驅動時,隻需要考慮到如何上報輸入事件即可,至于如何處理這些上報的輸入事件,是上層需要做的事情,無需關注。

二、platform平台驅動模型

1. 為什麼需要platform?

Linux核心中的驅動程式分離為:總線(bus)、驅動(driver)、裝置(device)模型,但是有些SOC中有些外設是沒有總線這個概念的。

為了解決此問題,Linux核心中提出了platform平台模型作為虛拟總線,相應的有platform_driver和platform_device。

2. platform總線

2.1. platform總線的類型——bus_type結構體

Linux核心中使用bus_type結構體表示總線,定義在檔案​

​include/linux/device.h​

​,如下:

struct bus_type {
  const char    *name;
  const char    *dev_name;
  struct device    *dev_root;
  struct device_attribute  *dev_attrs;  /* use dev_groups instead */
  const struct attribute_group **bus_groups;
  const struct attribute_group **dev_groups;
  const struct attribute_group **drv_groups;

  int (*match)(struct device *dev, struct device_driver *drv);
  int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
  int (*probe)(struct device *dev);
  int (*remove)(struct device *dev);
  void (*shutdown)(struct device *dev);

  int (*online)(struct device *dev);
  int (*offline)(struct device *dev);

  int (*suspend)(struct device *dev, pm_message_t state);
  int (*resume)(struct device *dev);

  const struct dev_pm_ops *pm;

  const struct iommu_ops *iommu_ops;

  struct subsys_private *p;
  struct lock_class_key lock_key;
};      

總線最重要的工作就是根據注冊的裝置來查找對應的驅動,或者根據注冊的驅動來查找相應的裝置,該任務主要依賴match函數指針,是以每一條總線都必須實作此函數。

int (*match)(struct device *dev, struct device_driver *drv);      

可以看到,match函數有兩個參數:dev和drv。

  • dev:device類型,表示裝置
  • drv:device_driver類型,表示驅動

2.2. platform總線

platform總線是bus_type的一個具體執行個體,定義在檔案​

​drivers/base/platform.c​

​中,如下:

struct bus_type platform_bus_type = {
  .name    = "platform",
  .dev_groups  = platform_dev_groups,
  .match    = platform_match,
  .uevent    = platform_uevent,
  .pm    = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);      

platform_bus_type就是platform平台總線,其中 platform_match 就是比對函數。

2.3. 驅動和裝置如何比對

platform_match 函數定義在​

​drivers/base/platform.c​

​中,

/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 *
 * Platform device IDs are assumed to be encoded like this:
 * "<name><instance>", where <name> is a short description of the type of
 * device, like "pci" or "floppy", and <instance> is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "<name>".  So, extract the <name> from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
 */
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);
}      

從代碼中可以看到,裝置和驅動的比對有四種方法。

(1)OF類型的比對(裝置樹采用的比對方式):根據裝置節點的 compatible 屬性和驅動的of_match_table表進行比對;

(2)ACPI比對方式

(3)id_table比對

(4)直接比較驅動和裝置的name字段是否相等

3. platform驅動(重點)

3.1. platform_driver結構體

platform_driver結構體表示platform驅動,定義在檔案​

​include/linux/platform_device.h​

​中,如下:

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;
};      

(1)probe函數

當驅動與裝置比對成功以後probe函數就會執行,由驅動的提供者編寫。

(2)remove函數

驅動解除安裝的時候會執行

(3)driver成員

device_driver相當于基類,提供了最基礎的驅動架構,platform_driver繼承了這個基類。

(4)id_table表

(5)of_match_table表:裝置樹比對表

3.2. platform驅動可用API

(1)向核心注冊一個platform驅動

/**
 * __platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 * @owner: owning module/driver
 */
int __platform_driver_register(struct platform_driver *drv,
        struct module *owner);

/*
 * use a macro to avoid include chaining to get THIS_MODULE
 */
#define platform_driver_register(drv)\
  __platform_driver_register(drv, THIS_MODULE)      

(2)從核心解除安裝一個platform驅動

extern void platform_driver_unregister(struct platform_driver *);      

4. platform裝置

繼續閱讀