4.1.1. platform总线

4.1.1.1. 概述

嵌入式系统中有很多物理总线: i2c、spi、USB、uart、PCIE等。linux中引入了一种驱动管理和注册的机制platform平台总线,它是一条虚拟的总线,并不是一个物理的总线。 相比于PCI、USB等它主要用于描述SOC上的片上资源,platform所描述的资源有一个共同点:在CPU的总线上直接取址

  1. platform总线相关代码: driver/base/platform.c

  2. 相关结构体定义: include/linux/platform_device.h

4.1.1.2. 数据结构

resource结构体保存了设备资源信息

//include/linux/ioport.h
struct resource {
        resource_size_t start; //资源起始地址
        resource_size_t end;   //资源结束地址
        const char *name;
        unsigned long flags;    //资源类型
        struct resource *parent, *sibling, *child;
};
  • platform_device

//include/linux/platform_device.h
struct platform_device {
        const char  *name;        //设备的名字
        int     id;                //ID 是用来区分如果设备名字相同的时候
        bool        id_auto;
        struct device   dev;       //设备结构
        u32     num_resources;     //resource结构个数
        struct resource *resource;  //设备资源

        const struct platform_device_id *id_entry;

        /* MFD cell pointer */
        struct mfd_cell *mfd_cell;

        /* arch specific additions */
        struct pdev_archdata    archdata;
};
  • platform_driver

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

4.1.1.3. resource

linux platform驱动中常见的几种资源如下:

#define IORESOURCE_IO           0x00000100      /* PCI/ISA I/O ports */
#define IORESOURCE_MEM          0x00000200
#define IORESOURCE_REG          0x00000300      /* Register offsets */
#define IORESOURCE_IRQ          0x00000400
#define IORESOURCE_DMA          0x00000800
#define IORESOURCE_BUS          0x00001000

我们主要分析前三种资源,即IO,MEM,REG

几乎每一种外设都是通过读写设备上的寄存器来进行的,外设的寄存器通常被连续的编址。根据CPU体系结构的不同,CPU对IO端口的编址方式有两种

  1. I/O映射方式(I/O-mapped)

典型的,如X86处理器为外设专门实现了一个单独的地址空间,称为i/o地址空间或i/o端口空间

  1. 内存映射方式(memory-mapped)

RISC指令系统的CPU(如ARM)通常只实现一个物理地址空间,像这种情况,外设的I/O端口的物理地址就被映射到内存地址空间中,外设I/O端口称为内存的一部分。此时CPU可以像访问一个内存单元 那样去访问外设I/O端口,而不需要设立专门的外设I/O指令

我们先分析一下资源树的总信息,然后再分析如何把资源加入到资源树中

[kernel/resource.c]
struct resource ioport_resource = {
    .name   = "PCI IO",
    .start  = 0,
    .end    = IO_SPACE_LIMIT,
    .flags  = IORESOURCE_IO,
};
EXPORT_SYMBOL(ioport_resource);

struct resource iomem_resource = {
    .name   = "PCI mem",
    .start  = 0,
    .end    = -1,
    .flags  = IORESOURCE_MEM,
};
EXPORT_SYMBOL(iomem_resource);

可以使用 cat /proc/iomem 来查看已经插入的资源

../../_images/cat_iomem.png

实际的插入动作由 __insert_resource函数完成

/*
 * Insert a resource into the resource tree. If successful, return NULL,
 * otherwise return the conflicting resource (compare to __request_resource())
 */
static struct resource * __insert_resource(struct resource *parent, struct resource *new)
{
        struct resource *first, *next;

        /* 注意这里是个循环,失败了也是会继续更新parrnt尝试插入的,除非返回值为NULL,或new相对与parent无效 */
        for (;; parent = first) {
                first = __request_resource(parent, new);    /* 返回NULL表示已经插入 */
                if (!first)
                        return first;    /* NULL表示已经插入,这里直接返回 */

                if (first == parent)        /* 插入的超过root范围或插入范围无效 */
                        return first;
                if (WARN_ON(first == new))      /* duplicated insertion 重复插入个资源 */
                        return first;

                /* 失败节点,的start大于new->start 或  失败节点的end小于new->end*/
                if ((first->start > new->start) || (first->end < new->end))
                        break;
                /* new和某段资源重复了 */
                if ((first->start == new->start) && (first->end == new->end))
                        break;
        }

        /* 到这里表示通过break出来的,即要插入的资源和资源池里面的资源有重叠 */
        for (next = first; ; next = next->sibling) {
                /* Partial overlap? Bad, and unfixable,部分重叠会认为是bad, */
                if (next->start < new->start || next->end > new->end)
                        return next;
                if (!next->sibling)
                        break;
                if (next->sibling->start > new->end)    /* 找到的某个资源的start大于new->end,即new资源整个都小于next->siblig,即可能可以做next->siblig节点的child */
                        break;
        }

        new->parent = parent;        /* new的parent指向根节点 */
        new->sibling = next->sibling;    /* new的sibling指向上一个节点(new < next) */
        new->child = first;            /* 挂接属于new范围内的资源到new里面 */

        next->sibling = NULL;
        for (next = first; next; next = next->sibling)
                next->parent = new;

        if (parent->child == first) {
                parent->child = new;
        } else {
                next = parent->child;
                while (next->sibling != first)
                        next = next->sibling;
                next->sibling = new;
        }
        return NULL;
}
  • 几个常用的资源注册函数

[lib/devres.c]
/**
 * devm_ioremap_resource() - check, request region, and ioremap resource
 * @dev: generic device to handle the resource for
 * @res: resource to be handled
 *
 * Checks that a resource is a valid memory region, requests the memory
 * region and ioremaps it. All operations are managed and will be undone
 * on driver detach.
 *
 * Returns a pointer to the remapped memory or an ERR_PTR() encoded error code
 * on failure. Usage example:
 *
 *  res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 *  base = devm_ioremap_resource(&pdev->dev, res);
 *  if (IS_ERR(base))
 *      return PTR_ERR(base);
 */
void __iomem *devm_ioremap_resource(struct device *dev,
                                  const struct resource *res)
{
    resource_size_t size;
    void __iomem *dest_ptr;

    BUG_ON(!dev);

        //通常ioremap的都是mem资源,如果不是则退出
    if (!res || resource_type(res) != IORESOURCE_MEM) {
          dev_err(dev, "invalid resource\n");
          return IOMEM_ERR_PTR(-EINVAL);
    }

    size = resource_size(res);  //得到资源的大小

        //请求资源
    if (!devm_request_mem_region(dev, res->start, size, dev_name(dev))) {
          dev_err(dev, "can't request region for resource %pR\n", res);
          return IOMEM_ERR_PTR(-EBUSY);
    }
        //ioremap映射物理地址到虚拟地址
    dest_ptr = devm_ioremap(dev, res->start, size);
    if (!dest_ptr) {
          dev_err(dev, "ioremap failed for resource %pR\n", res);
          devm_release_mem_region(dev, res->start, size);
          dest_ptr = IOMEM_ERR_PTR(-ENOMEM);
    }

    return dest_ptr;
}
EXPORT_SYMBOL(devm_ioremap_resource);
#define devm_request_mem_region(dev,start,n,name) \
__devm_request_region(dev, &iomem_resource, (start), (n), (name))

  struct resource *
  __devm_request_region(struct device *dev, struct resource *parent,
                                resource_size_t start, resource_size_t n, const char *name)
  {
          struct region_devres *dr = NULL;
          struct resource *res;

          //申请空间,管理资源
          dr = devres_alloc(devm_region_release, sizeof(struct region_devres),
                                GFP_KERNEL);
          if (!dr)
                  return NULL;

          dr->parent = parent;  //初始化
          dr->start = start;
          dr->n = n;

          res = __request_region(parent, start, n, name, 0);
          if (res)
                  devres_add(dev, dr);  //把申请到的资源加入到该设备中
          else
                  devres_free(dr);

          return res;
  }
  EXPORT_SYMBOL(__devm_request_region);
      /**
 * __request_region - create a new busy resource region
 * @parent: parent resource descriptor
 * @start: resource start address
 * @n: resource region size
 * @name: reserving caller's ID string
 * @flags: IO resource flags
 */
struct resource * __request_region(struct resource *parent,
                   resource_size_t start, resource_size_t n,
                   const char *name, int flags)
{
    DECLARE_WAITQUEUE(wait, current);
    struct resource *res = alloc_resource(GFP_KERNEL);

    if (!res)
        return NULL;

    //填充资源信息
    res->name = name;
    res->start = start;
    res->end = start + n - 1;

    write_lock(&resource_lock);

    for (;;) {
        struct resource *conflict;

        res->flags = resource_type(parent) | resource_ext_type(parent);
        res->flags |= IORESOURCE_BUSY | flags;
        res->desc = parent->desc;

        conflict = __request_resource(parent, res);
        if (!conflict)
            break;
        /*
         * mm/hmm.c reserves physical addresses which then
         * become unavailable to other users.  Conflicts are
         * not expected.  Warn to aid debugging if encountered.
         */
        if (conflict->desc == IORES_DESC_DEVICE_PRIVATE_MEMORY) {
            pr_warn("Unaddressable device %s %pR conflicts with %pR",
                conflict->name, conflict, res);
        }
        if (conflict != parent) {
            if (!(conflict->flags & IORESOURCE_BUSY)) {
                parent = conflict;
                continue;
            }
        }
        //如果这个资源是多个设备使用的话,把这个设备加入到等待队列,资源可用时再唤醒
        if (conflict->flags & flags & IORESOURCE_MUXED) {
            add_wait_queue(&muxed_resource_wait, &wait);
            write_unlock(&resource_lock);
            set_current_state(TASK_UNINTERRUPTIBLE);
            schedule();

4.1.1.4. platform_device注册

[drivers/base/platform.c]
int platform_device_register(struct platform_device *pdev)
{
        device_initialize(&pdev->dev);
                |--dev->kobj.kset = devices_kset;
                |--kobject_init(&dev->kobj, &device_ktype);
                |--INIT_LIST_HEAD(&dev->dma_pools);
                |--mutex_init(&dev->mutex);
                |--lockdep_set_novalidate_class(&dev->mutex);
                |--spin_lock_init(&dev->devres_lock);
                |--INIT_LIST_HEAD(&dev->devres_head);
                |--device_pm_init(dev);
                |--set_dev_node(dev, -1);
                |--INIT_LIST_HEAD(&dev->links.consumers);
                |--INIT_LIST_HEAD(&dev->links.suppliers);
                |--dev->links.status = DL_DEV_NO_DRIVER;
        arch_setup_pdev_archdata(pdev);
        return platform_device_add(pdev);
}
int platform_device_add(struct platform_device *pdev)
{
        u32 i;
        int ret;

        if (!pdev)
                return -EINVAL;

        if (!pdev->dev.parent)
                pdev->dev.parent = &platform_bus;       //设置总线的父设备为平台设备

        pdev->dev.bus = &platform_bus_type;             //设备挂接在平台总线下

        switch (pdev->id) {
        default:        //设置设备标号
                dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);
                break;
        case PLATFORM_DEVID_NONE:       //-1表示不需要设备标号
                dev_set_name(&pdev->dev, "%s", pdev->name);
                break;
        case PLATFORM_DEVID_AUTO:       //由总线自行分配
                /*
                 * Automatically allocated device ID. We mark it as such so
                 * that we remember it must be freed, and we append a suffix
                 * to avoid namespace collision with explicit IDs.
                 */
                ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
                if (ret < 0)
                        goto err_out;
                pdev->id = ret;
                pdev->id_auto = true;
                dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
                break;
        }

        //对该设备的资源插入资源树
        for (i = 0; i < pdev->num_resources; i++) {
                struct resource *p, *r = &pdev->resource[i];

                if (r->name == NULL)
                        r->name = dev_name(&pdev->dev); //如果没有设置资源名字则和设备名一样

                p = r->parent;
                if (!p) {
                        if (resource_type(r) == IORESOURCE_MEM) //如果没有父节点则检查资源是否是MEM或者IO
                                p = &iomem_resource;    //iomem资源树
                        else if (resource_type(r) == IORESOURCE_IO)
                                p = &ioport_resource;
                }

                if (p) {
                        ret = insert_resource(p, r);    //插入资源
                        if (ret) {
                                dev_err(&pdev->dev, "failed to claim resource %d: %pR\n", i, r);
                                goto failed;
                        }
                }
        }

        pr_debug("Registering platform device '%s'. Parent at %s\n",
                 dev_name(&pdev->dev), dev_name(pdev->dev.parent));

        //增加设备,节点,sysfs信息
        ret = device_add(&pdev->dev);
        if (ret == 0)
                return ret;

 failed:
        if (pdev->id_auto) {
                ida_simple_remove(&platform_devid_ida, pdev->id);
                pdev->id = PLATFORM_DEVID_AUTO;
        }

        while (i--) {
                struct resource *r = &pdev->resource[i];
                if (r->parent)
                        release_resource(r);
        }

 err_out:
        return ret;
}