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, 内核对设备树的处理




Linux Kernel(17.1)- Basic Device Tree syntax


這篇會介紹一下Device Tree的基本資料型態,並透過觀察/sys/firmware/devicetree/讓你更貼近一下DT的資料結構,下面範例是在vexpress-v2p-ca9.dts中include "brook.dtsi",然後在"brook.dtsi"撰寫DT的基本語法。DT的每個node,可包含零個以上的properties或child node。
brook@vista:~/qemu/linux-arm$ vim arch/arm/boot/dts/vexpress-v2p-ca9.dts
 #include "vexpress-v2m.dtsi"
 #include "brook.dtsi" /* add this line */

brook@vista:~/qemu/linux-arm$ vim arch/arm/boot/dts/brook.dtsi 

/ {
    node1 {
        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 {
        };
    };
    node2 {
        an-empty-property;
        a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
        // [@], 
        //  is a simple ascii string and can be up to 31 characters in length.
        child-node@1 {
        };
        child-node@2 {
        };
    };
};


brook@vista:~/qemu/linux-arm$ export ARCH=arm
brook@vista:~/qemu/linux-arm$ export CROSS_COMPILE=arm-linux-gnueabihf-
brook@vista:~/qemu/linux-arm$ export PATH=/opt/gcc-linaro-7.4.1-2019.02-x86_64_arm-linux-gnueabihf/bin:$PATH
brook@vista:~/qemu/linux-arm$ make dtbs


properties的value可以是empty或是以下資料型態:
  • Text strings (null terminated) are represented with double quotes: string-property = "a string";
  • 'Cells' are 32 bit unsigned integers delimited by angle brackets: cell-property = <0xbeef 123 0xabcd1234>;
  • Binary data is delimited with square brackets: binary-property = [0x01 0x23 0x45 0x67];
  • Data of differing representations can be concatenated together using a comma: mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
  • Commas are also used to create lists of strings: string-list = "red fish", "blue fish";


brook@vista:~/qemu/linux-arm# cd ..
brook@vista:~/qemu$ qemu-system-arm -M vexpress-a9 -m 512M -kernel ./linux-arm/arch/arm/boot/zImage -dtb ./linux-arm/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -initrd ./initrd-arm.img -nographic -append "console=ttyAMA0"

----- boot to VM -----

Please press Enter to activate this console.
/ # ls /sys/firmware/
devicetree  fdt
/ # ls /sys/firmware/devicetree/
base
/ # ls /sys/firmware/devicetree/base/ #根目錄(/sys/firmware/devicetree/base/) 多了node1與node2
/ # ls /sys/firmware/devicetree/base/
#address-cells                 model
#size-cells                    name
aliases                        node1
arm,hbi                        node2
arm,vexpress,site              pmu
cache-controller@1e00a000      reserved-memory
chosen                         scu@1e000000
clcd@10020000                  smb@4000000
compatible                     timer@100e4000
cpus                           timer@1e000600
dcc                            virtio_mmio@10013000
hsb@e0000000                   virtio_mmio@10013200
interrupt-controller@1e001000  virtio_mmio@10013400
interrupt-parent               virtio_mmio@10013600
memory-controller@100e0000     watchdog@100e5000
memory-controller@100e1000     watchdog@1e000620
memory@60000000

/ # find /sys/firmware/devicetree/base/node1
/sys/firmware/devicetree/base/node1
/sys/firmware/devicetree/base/node1/child-node2
/sys/firmware/devicetree/base/node1/child-node2/name
/sys/firmware/devicetree/base/node1/a-string-property
/sys/firmware/devicetree/base/node1/a-string-list-property
/sys/firmware/devicetree/base/node1/a-byte-data-property
/sys/firmware/devicetree/base/node1/name
/sys/firmware/devicetree/base/node1/child-node1
/sys/firmware/devicetree/base/node1/child-node1/first-child-property
/sys/firmware/devicetree/base/node1/child-node1/second-child-property
/sys/firmware/devicetree/base/node1/child-node1/a-string-property
/sys/firmware/devicetree/base/node1/child-node1/name


/ # find /sys/firmware/devicetree/base/node2
/sys/firmware/devicetree/base/node2
/sys/firmware/devicetree/base/node2/child-node@1
/sys/firmware/devicetree/base/node2/child-node@1/name
/sys/firmware/devicetree/base/node2/child-node@2
/sys/firmware/devicetree/base/node2/child-node@2/name
/sys/firmware/devicetree/base/node2/a-cell-property
/sys/firmware/devicetree/base/node2/name
/sys/firmware/devicetree/base/node2/an-empty-property


/ # hexdump -e '8/1 "%02X ""\t"" "' -e '8/1 "%c""\n"' /sys/firmware/devicetree/base/node1/a-string-property
41 20 73 74 72 69 6E 67  A string
00

/ # hexdump -e '8/1 "%02X ""\t"" "' -e '8/1 "%c""\n"' /sys/firmware/devicetree/base/node1/a-string-list-property
66 69 72 73 74 20 73 74  first st
72 69 6E 67 00 73 65 63  ringsec
6F 6E 64 20 73 74 72 69  ond stri
6E 67 00                 ng
strings lists中的element是"0"分隔

/ # hexdump -e '8/1 "%02X ""\t"" "' -e '8/1 "%c""\n"' /sys/firmware/devicetree/base/node1/a-byte-data-property
01 23 34 56              #4V
byte-data如其名,每個值大小就是一個byte

/ # hexdump -e '8/1 "%02X ""\t"" "' -e '8/1 "%c""\n"' /sys/firmware/devicetree/base/node1/name
6E 6F 64 65 31 00        node1

/ # hexdump -e '8/1 "%02X ""\t"" "' -e '8/1 "%c""\n"' /sys/firmware/devicetree/base/node1/child-node1/first-child-property
empty

/ # hexdump -e '8/1 "%02X ""\t"" "' -e '8/1 "%c""\n"' /sys/firmware/devicetree/base/node1/child-node1/second-child-property
00 00 00 01

/ # hexdump -e '8/1 "%02X ""\t"" "' -e '8/1 "%c""\n"' /sys/firmware/devicetree/base/node1/child-node1/a-string-property
48 65 6C 6C 6F 2C 20 77  Hello, w
6F 72 6C 64 00           orld

/ # hexdump -e '8/1 "%02X ""\t"" "' -e '8/1 "%c""\n"' /sys/firmware/devicetree/base/node2/a-cell-property
00 00 00 01 00 00 00 02
00 00 00 03 00 00 00 04
cell每個value大小為32byte

/ # hexdump -e '8/1 "%02X ""\t"" "' -e '8/1 "%c""\n"' /sys/firmware/devicetree/base/node2/child-node@1/name
63 68 69 6C 64 2D 6E 6F  child-no
64 65 00                 de

/ # hexdump -e '8/1 "%02X ""\t"" "' -e '8/1 "%c""\n"' /sys/firmware/devicetree/base/node2/child-node@2/name
63 68 69 6C 64 2D 6E 6F  child-no
64 65 00                 de


    參考資料:
  • https://elinux.org/Device_Tree_Usage, Device Tree Usage
  • https://elinux.org/images/f/f9/Petazzoni-device-tree-dummies_0.pdf, device tree dumies
  • https://blog.csdn.net/RadianceBlau/article/details/70800076, Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇)




熱門文章