2020年2月28日 星期五

Linux Kernel(16.2)- PHY Abstraction Layer I


Purpose

多數的network devices都是透過MAC去存取PHY上的register,而network device driver就是利用這些register去決定如何配置該網路裝置, PHY的register都是遵守相同的標準,因而建立PHY Abstraction Layer的目的就是減少network device driver的loading,因為PHY的driver在此了, 三個目的如下:
  • 1) Increase code-reuse
  • 2) Increase overall code-maintainability
  • 3) Speed development time for new network drivers, and for new systems


The MDIO bus

多數的network devices都是透過所謂的management bus與PHY溝通,不同的network devices會使用不同的bus, 這些bus要被註冊到對應的network devices上,這些bus的interface必須遵守以下規則
  1. read and write function must be implemented.
    int write(struct mii_bus *bus, int mii_id, int regnum, u16 value);
    int read(struct mii_bus *bus, int mii_id, int regnum);
    mii_id是PHY在該bus上的ID
  2. reset function可有可無
  3. probe function是必要的,用以設定PHY driver所需的任何東西,
可以參考drivers/net/ethernet/freescale/fsl_pq_mdio.c為例的mdio bus driver,以下為我摘要的片段
static struct platform_driver fsl_pq_mdio_driver = {
    .driver = {
        .name = "fsl-pq_mdio",
        .of_match_table = fsl_pq_mdio_match,
    },
    .probe = fsl_pq_mdio_probe,
    .remove = fsl_pq_mdio_remove,
};

static int fsl_pq_mdio_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    struct mii_bus *new_bus;

    new_bus = mdiobus_alloc_size(sizeof(*priv));
    if (!new_bus)
        return -ENOMEM;

    priv = new_bus->priv;

    new_bus->name = "Freescale PowerQUICC MII Bus",
    new_bus->read = &fsl_pq_mdio_read;
    new_bus->write = &fsl_pq_mdio_write;
    new_bus->reset = &fsl_pq_mdio_reset;

    new_bus->parent = &pdev->dev;
    platform_set_drvdata(pdev, new_bus);
    err = of_mdiobus_register(new_bus, np);

    return 0;
}

/*
 * Read the bus for PHY at addr mii_id, register regnum, and return the value.
 * Clears miimcom first.
 *
 * All PHY operation done on the bus attached to the local interface, which
 * may be different from the generic mdio bus.  This is helpful in programming
 * interfaces like the TBI which, in turn, control interfaces like on-chip
 * SERDES and are always tied to the local mdio pins, which may not be the
 * same as system mdio bus, used for controlling the external PHYs, for eg.
 */
static int fsl_pq_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
{
    struct fsl_pq_mdio_priv *priv = bus->priv;
    struct fsl_pq_mii __iomem *regs = priv->regs;
    unsigned int timeout;
    u16 value;

    /* Set the PHY address and the register address we want to read */
    iowrite32be((mii_id << 8) | regnum, ®s->miimadd);

    /* Clear miimcom, and then initiate a read */
    iowrite32be(0, ®s->miimcom);
    iowrite32be(MII_READ_COMMAND, ®s->miimcom);

    /* Wait for the transaction to finish, normally less than 100us */
    timeout = MII_TIMEOUT;
    while ((ioread32be(®s->miimind) &
           (MIIMIND_NOTVALID | MIIMIND_BUSY)) && timeout) {
        cpu_relax();
        timeout--;
    }

    if (!timeout)
        return -ETIMEDOUT;


    /* Grab the value of the register from miimstat */
    value = ioread32be(®s->miimstat);

    dev_dbg(&bus->dev, "read %04x from address %x/%x\n", value, mii_id, regnum);
    return value;
}

/*
 * Write value to the PHY at mii_id at register regnum, on the bus attached
 * to the local interface, which may be different from the generic mdio bus
 * (tied to a single interface), waiting until the write is done before
 * returning. This is helpful in programming interfaces like the TBI which
 * control interfaces like onchip SERDES and are always tied to the local
 * mdio pins, which may not be the same as system mdio bus, used for
 * controlling the external PHYs, for example.
 */
static int fsl_pq_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
        u16 value)
{
    struct fsl_pq_mdio_priv *priv = bus->priv;
    struct fsl_pq_mii __iomem *regs = priv->regs;
    unsigned int timeout;

    /* Set the PHY address and the register address we want to write */
    iowrite32be((mii_id << 8) | regnum, ®s->miimadd);

    /* Write out the value we want */
    iowrite32be(value, ®s->miimcon);

    /* Wait for the transaction to finish */
    timeout = MII_TIMEOUT;
    while ((ioread32be(®s->miimind) & MIIMIND_BUSY) && timeout) {
        cpu_relax();
        timeout--;
    }

    return timeout ? 0 : -ETIMEDOUT;
}

/* Reset the MIIM registers, and wait for the bus to free */
static int fsl_pq_mdio_reset(struct mii_bus *bus)
{
    struct fsl_pq_mdio_priv *priv = bus->priv;
    struct fsl_pq_mii __iomem *regs = priv->regs;
    unsigned int timeout;

    mutex_lock(&bus->mdio_lock);

    /* Reset the management interface */
    iowrite32be(MIIMCFG_RESET, ®s->miimcfg);

    /* Setup the MII Mgmt clock speed */
    iowrite32be(MIIMCFG_INIT_VALUE, ®s->miimcfg);

    /* Wait until the bus is free */
    timeout = MII_TIMEOUT;
    while ((ioread32be(®s->miimind) & MIIMIND_BUSY) && timeout) {
        cpu_relax();
        timeout--;
    }

    mutex_unlock(&bus->mdio_lock);

    if (!timeout) {
        dev_err(&bus->dev, "timeout waiting for MII bus\n");
        return -EBUSY;
    }

    return 0;
}


(RG)MII/electrical interface considerations

Due to this design decision, a 1.5ns to 2ns delay must be added between the clock line (RXC or TXC) and the data lines to let the PHY (clock sink) have enough setup and hold times to sample the data lines correctly.



我也摘錄一段Qualcomm的EMAC probe部分,
https://github.com/mauronofrio/android_kernel_nubia_msm8953/blob/master/drivers/net/ethernet/qualcomm/emac/emac_phy.c
int emac_phy_config_external(struct platform_device *pdev,
                 struct emac_adapter *adpt)
{
    struct device_node *np = pdev->dev.of_node;
    struct mii_bus *mii_bus;
    int ret;

    /* Create the mii_bus object for talking to the MDIO bus */
    mii_bus = devm_mdiobus_alloc(&pdev->dev);
    adpt->mii_bus = mii_bus;

    if (!mii_bus)
        return -ENOMEM;

    mii_bus->name = "emac-mdio";
    snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s", pdev->name);
    mii_bus->read = emac_mdio_read;
    mii_bus->write = emac_mdio_write;
    mii_bus->parent = &pdev->dev;
    mii_bus->priv = adpt;

    if (ACPI_COMPANION(&pdev->dev)) {
        u32 phy_addr;

        ret = mdiobus_register(mii_bus);
        if (ret) {
            emac_err(adpt, "could not register mdio bus\n");
            return ret;
        }
        ret = device_property_read_u32(&pdev->dev, "phy-channel",
                           &phy_addr);
        if (ret)
            /* If we can't read a valid phy address, then assume
             * that there is only one phy on this mdio bus.
             */
            adpt->phydev = phy_find_first(mii_bus);
        else
            adpt->phydev = mii_bus->phy_map[phy_addr];
    } else {
        struct device_node *phy_np;

        ret = of_mdiobus_register(mii_bus, np);

        if (ret) {
            emac_err(adpt, "could not register mdio bus\n");
            return ret;
        }

        phy_np = of_parse_phandle(np, "phy-handle", 0);
        adpt->phydev = of_phy_find_device(phy_np);
        of_node_put(phy_np);
    }

    if (!adpt->phydev) {
        emac_err(adpt, "could not find external phy\n");
        mdiobus_unregister(mii_bus);
        return -ENODEV;
    }

    if (!adpt->phydev->phy_id) {
        emac_err(adpt, "External phy is not up\n");
        mdiobus_unregister(mii_bus);
        return -EPROBE_DEFER;
    }

    if (adpt->phydev->drv) {
        emac_dbg(adpt, probe, "attached PHY driver [%s] ",
             adpt->phydev->drv->name);
        emac_dbg(adpt, probe, "(mii_bus:phy_addr=%s, irq=%d)\n",
             dev_name(&adpt->phydev->dev), adpt->phydev->irq);
    }

    /* Set initial link status to false */
    adpt->phydev->link = 0;
    return 0;
}

/**
 * of_mdiobus_register - Register mii_bus and create PHYs from the device tree
 * @mdio: pointer to mii_bus structure
 * @np: pointer to device_node of MDIO bus.
 *
 * This function registers the mii_bus structure and registers a phy_device
 * for each child node of @np.
 */
int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
{
    struct device_node *child;
    const __be32 *paddr;
    bool scanphys = false;
    int addr, rc, i;

    /* Mask out all PHYs from auto probing.  Instead the PHYs listed in
     * the device tree are populated after the bus has been registered */
    mdio->phy_mask = ~0;

    /* Clear all the IRQ properties */
    if (mdio->irq)
        for (i=0; iirq[i] = PHY_POLL;

    mdio->dev.of_node = np;

    /* Register the MDIO bus */
    rc = mdiobus_register(mdio);
    if (rc)
        return rc;


    /* Loop over the child nodes and register a phy_device for each one */
    for_each_available_child_of_node(np, child) {
        addr = of_mdio_parse_addr(&mdio->dev, child);
        if (addr < 0) {
            scanphys = true;
            continue;
        }

        rc = of_mdiobus_register_phy(mdio, child, addr);
        if (rc)
            continue;
    }

    if (!scanphys)
        return 0;

    /* auto scan for PHYs with empty reg property */
    for_each_available_child_of_node(np, child) {
        /* Skip PHYs with reg property set */
        paddr = of_get_property(child, "reg", NULL);
        if (paddr)
            continue;

        for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
            /* skip already registered PHYs */
            if (mdio->phy_map[addr])
                continue;

            /* be noisy to encourage people to set reg property */
            dev_info(&mdio->dev, "scan phy %s at address %i\n",
                 child->name, addr);

            rc = of_mdiobus_register_phy(mdio, child, addr);
            if (rc)
                continue;
        }
    }

    return 0;
}



這兩個sample code都是簡略的顯示management bus的部分,主要展示mdio bus device driver的基本寫法,填寫name、read()、write()、reset()後使用of_mdiobus_register()註冊,

    參考資料:
  • kernel/msm-4.14/Documentation/networking/phy.txt



2020年2月9日 星期日

busybox init flow


Linux在啟動kernel後,便由kernel載入init程式,由init程式完成餘下的啟動過程,簡略片段如下
Kernel 4.14
start_kernel()
  |--> rest_init()
    |--> pid = kernel_thread(kernel_init, NULL, CLONE_FS);
      |--> kernel_init()
       if (!try_to_run_init_process("/sbin/init") ||
          !try_to_run_init_process("/etc/init") ||
          !try_to_run_init_process("/bin/init") ||
          !try_to_run_init_process("/bin/sh"))
          return 0;
       panic("No working init found.  Try passing init= option to kernel. "
            "See Linux Documentation/admin-guide/init.rst for guidance.");

接著就會交由init開始一連串的init過程,busybox的init會去讀取/etc/inittab設定來進行init,如果沒有/etc/inittab,會執行以下預設的inittab內容
::sysinit:/etc/init.d/rcS
::askfirst:/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
tty2::askfirst:/bin/sh
tty3::askfirst:/bin/sh
tty4::askfirst:/bin/sh

其格式為<id>:<runlevels>:<action>:<process>,說明如下
<id>: 與傳統init意思不同, 會把stdin/stdout設為/dev/<id>

<runlevels>: busybox會ignore該欄位

<action>: 有效的action包含sysinit, wait, once, respawn, askfirst, shutdown, restart and ctrlaltdel.
  "sysinit" 第一個被執行的action, init會等待"sysinit"執行完成後, 再執行wait與once
  "askfirst"/"respawn" 接著被執行, 但是askfirst執行前會先印"Please press Enter to activate this console". 

<process>: 要執行的命令

inittab的action執行code flow如下
# busybox v1.32

static pid_t run(const struct init_action *a)
{
  ...
  if (BB_MMU && (a->action_type & ASKFIRST)) {
    static const char press_enter[] ALIGN1 =
#ifdef CUSTOMIZED_BANNER
#include CUSTOMIZED_BANNER
#endif
        "\nPlease press Enter to activate this console. ";
    full_write(STDOUT_FILENO, press_enter, sizeof(press_enter) - 1);
    ...
  }
  ...
}

static void run_actions(int action_type)
{
  struct init_action *a;

  for (a = G.init_action_list; a; a = a->next) {
    if (!(a->action_type & action_type))
      continue;

    if (a->action_type & (SYSINIT | WAIT | ONCE | CTRLALTDEL | SHUTDOWN)) {
        pid_t pid = run(a);
      if (a->action_type & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN))
          waitfor(pid);
    }
    if (a->action_type & (RESPAWN | ASKFIRST)) {
      /* Only run stuff with pid == 0. If pid != 0,
       * it is already running
       */
      if (a->pid == 0)
        a->pid = run(a);
    }
  }
}

int init_main(int argc UNUSED_PARAM, char **argv)
{
  ...
  /* Check if we are supposed to be in single user mode */
  if (argv[1]
   && (strcmp(argv[1], "single") == 0 || strcmp(argv[1], "-s") == 0 || LONE_CHAR(argv[1], '1'))
  ) {
    /* ??? shouldn't we set RUNLEVEL="b" here? */
    /* Start a shell on console */
    new_init_action(RESPAWN, bb_default_login_shell, "");
  } else {
    /* Not in single user mode - see what inittab says */

    /* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
    * then parse_inittab() simply adds in some default
    * actions (i.e., INIT_SCRIPT and a pair
    * of "askfirst" shells) */
    parse_inittab();
  }

  ...
  /* Now run everything that needs to be run */
  /* First run the sysinit command */
  run_actions(SYSINIT);
  check_delayed_sigs(&G.zero_ts);
  /* Next run anything that wants to block */
  run_actions(WAIT);
  check_delayed_sigs(&G.zero_ts);
  /* Next run anything to be run only once */
  run_actions(ONCE);
  ...
  while (1) {
    ...
    /* (Re)run the respawn/askfirst stuff */
    run_actions(RESPAWN | ASKFIRST);
    ...
  }
  ...
}


如果要進入signle user mode,就需要傳遞"single"或"-S"給init當參數,要把字串放在kernel parameters "--" 之後
The kernel parses parameters from the kernel command line up to “–”; 
if it doesn’t recognize a parameter and it doesn’t contain a ‘.’, 
the parameter gets passed to init: parameters with ‘=’ go into init’s environment, 
others are passed as command line arguments to init.
Everything after “–” is passed as an argument to init.

如:
qemu-system-arm -M vexpress-a9 -m 512M -kernel ./linux/arch/arm/boot/zImage -dtb \
./linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -initrd ./initrd-arm.img \
-nographic -append "console=ttyAMA0 -- single"

busybox並不支援runlevel
    參考資料:
  • https://en.wikipedia.org/wiki/Init, init
  • https://en.wikipedia.org/wiki/Runlevel, runlevel
  • https://git.busybox.net/busybox/tree/examples/inittab, busybox example for inittab
  • https://www.itread01.com/p/1350587.html, kernel 啟動流程之 【裝置驅動載入】 學習筆記
  • https://www.itread01.com/content/1543246633.html, busybox(一)淺析

2019年8月18日 星期日

Linux Kernel(17.2)- Device Tree and Platform Device


如同Linux Kernel(15.3)- The Linux usage model for device tree data所描述,init_machine()透過呼叫of_platform_populate()建構platform device。
kernel v3.5
|-->> msm8x60_dt_init(void) @arch/arm/mach-msm/board-msm8x60.c
  |--> of_platform_populate() @drivers/of/platform.c
    |--> of_platform_bus_create() @drivers/of/platform.c
      /* Make sure it has a compatible property */ 
          那些具有compatible的node都會被轉成platform device
      if (strict && (!of_get_property(bus, "compatible", NULL))) {
        pr_debug("%s() - skipping %s, no compatible prop\n",
                 __func__, bus->full_name);
        return 0;
      }

      ...
      for_each_child_of_node(bus, child) {
        pr_debug("create child: %s\n", child->full_name);
        rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
        if (rc) {
          of_node_put(child);
          break;
        }
      }


接著會在註冊driver時, 呼叫of_driver_match_device()進行match
kernel v3.5
|--> platform_driver_register() @drivers/base/platform.c 
  |--> driver_register() @drivers/base/driver.c
    |--> bus_add_driver() @drivers/base/bus.c /* 建立sysfs file node 與 attr */  
      |--> driver_attach() @drivers/base/dd.c 
        |--> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach) @bus.c  
          |--> __driver_attach @drivers/base/dd.c
            |--> driver_match_device(drv, dev) @base.h
              |--> platform_match(); @drivers/base/platform.c
                if (of_driver_match_device(dev, drv))
                  return 1;

          |--> __driver_attach @drivers/base/dd.c
            if (!dev->driver)
              driver_probe_device(drv, dev);
            |--> driver_probe_device @drivers/base/dd.c
              ret = really_probe(dev, drv);
              |--> really_probe @drivers/base/dd.c
                if (dev->bus->probe) {
                  ret = dev->bus->probe(dev);

                if (ret)
                  goto probe_failed;
                } else if (drv->probe) {
                  ret = drv->probe(dev);
                  if (ret)
                    goto probe_failed;
                }

----- @include/linux/of_device.h -----
of_driver_match_device(struct device *dev, const struct device_driver *drv) 
{
    return of_match_device(drv->of_match_table, dev) != NULL;
}

----- @drivers/of/device.c -----
const struct of_device_id *of_match_device(const struct of_device_id *matches,
        const struct device *dev)
{
    if ((!matches) || (!dev->of_node))
        return NULL;
    return of_match_node(matches, dev->of_node);
}

----- @drivers/of/base.c -----
const struct of_device_id *of_match_node(const struct of_device_id *matches,
      const struct device_node *node)
{
    if (!matches)
        return NULL;

    while (matches->name[0] || matches->type[0] || matches->compatible[0]) {
        int match = 1;
        if (matches->name[0])
            match &= node->name && !strcmp(matches->name, node->name);
        if (matches->type[0])
            match &= node->type && !strcmp(matches->type, node->type);
        if (matches->compatible[0])
            match &= of_device_is_compatible(node, matches->compatible);
        if (match)
            return matches;
        matches++;
    }
    return NULL;
}


修改一下DTS驗證一下platform device
/ {
    node1 {
        compatible = "brook,dts1";
        a-string-property = "A string";
        a-string-list-property = "first string", "second string";
        // hex is implied in byte arrays. no '0x' prefix is required
        a-byte-data-property = [01 23 34 56];
        child-node1 {
            first-child-property;
            second-child-property = <1>;
            a-string-property = "Hello, world";
        };
        child-node2 {
        };
    };
};


/ # ls /sys/bus/platform/devices
10000000.sysreg
10002000.i2c
...
node1
...

/ # cd /sys/bus/platform/devices/node1/
/sys/devices/platform/node1 # ls
driver_override  of_node          subsystem
modalias         power            uevent
/sys/devices/platform/node1 # ls of_node/
a-byte-data-property    child-node1             name
a-string-list-property  child-node2
a-string-property       compatible
/sys/devices/platform/node1 # ftpget 192.168.1.1 /tmp/brook_modules.ko /home/brook/my_driver/brook_modules.ko
/sys/devices/platform/node1 # insmod /tmp/brook_modules.ko
brook_modules: loading out-of-tree module taints kernel.
brook_init(#55)
brook_probe(#21)


基本上與傳統的platform device driver的差異是:
  1. device由DTS的compatible產生,無須呼叫platform_device_register()註冊device
  2. 需在platform_driver.driver.of_match_table中掛上of_device_id[],裡面的compatible會與DTS的compatible進行比對
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#include <linux/of.h>
#include <linux/of_device.h>

MODULE_AUTHOR("Brook");
MODULE_DESCRIPTION("Kernel module for demo");
MODULE_LICENSE("GPL");

#define DEVNAME "brook"

static struct platform_device brook_device = {
    .name = DEVNAME,
};

static int brook_probe(struct platform_device *pdev)
{
    pr_info("%s(#%d)\n", __func__, __LINE__);
    return 0;
}

static int brook_remove(struct platform_device *pdev)
{
    pr_info("%s(#%d)\n", __func__, __LINE__);
    return 0;
}

static struct of_device_id brook_dt_ids[] = {
    {
        .compatible = "brook,dts1",
    }, {
        .compatible = "brook,dts2",
    }, {
    }
};

MODULE_DEVICE_TABLE(of, brook_dt_ids);

static struct platform_driver brook_driver = {
    .driver = {
        .name = DEVNAME,
        .owner = THIS_MODULE,
        .of_match_table = brook_dt_ids,
    },
    .probe = brook_probe,
    .remove = brook_remove,
};

static int __init brook_init(void)
{
    int ret;
    pr_info("%s(#%d)\n", __func__, __LINE__);

    ret = platform_driver_register(&brook_driver);
    if (ret) {
        dev_err(&(brook_device.dev),
                "%s(#%d): platform_driver_register fail(%d)\n", __func__,
                __LINE__, ret);
    }
    return ret;
}
module_init(brook_init);

static void __exit brook_exit(void)
{
    dev_info(&(brook_device.dev), "%s(#%d)\n", __func__, __LINE__);
    platform_driver_unregister(&brook_driver);
}
module_exit(brook_exit);


    參考資料:
  • http://wiki.dreamrunner.org/public_html/Embedded-System/Linux-Device-Tree.html, Linux Device tree
  • Linux Kernel(15.3)- The Linux usage model for device tree data
  • http://wiki.100ask.org/%E7%AC%AC%E4%B8%89%E8%AF%BE:%E5%86%85%E6%A0%B8%E5%AF%B9%E8%AE%BE%E5%A4%87%E6%A0%91%E7%9A%84%E5%A4%84%E7%90%86, 内核对设备树的处理




熱門文章