2020年11月28日 星期六

LTE-U and LAA


LTE-Unliceded(LTE-U/3GPP Rel-10/11/12)最早是由Qualcomm提出的LTE標準,旨在允許Operator透過ISM(Industrial Scientific Medical Band/5GHz)頻帶取得更大的頻寬。control channel需要走LTE,而data走5GHz 頻段,所以要搭配CA(Carrier Aggregation)以及SDL(Supplemental Downlink)完成該功能,該5G頻段為LTE Band 46。

LAA(Licensed Assisted Access)是LTE-U的進化版,並提入3GPP Rel-13,隨後還有3GPP Rel-14的eLAA(enhanced-LAA)以及3GPP Rel-14的feLAA(Further Enhanced LAA)。

LTE-U 與 LAA 主要的差異在於採用不同的避免干擾機制(contention protocol),LTE-U 主要採取CSAT(Carrier Sense Adaptation Transmission)而LAA採用LBT through CCA((Listen Before Talk through clear channel assessment) ,兩種模式皆是為了與既有 Wi-Fi 服務於免執照頻段中共享與共存。





2020年11月14日 星期六

Linux Kernel(18.1)- My First Filesystem


這一個章節要跟大家介紹如何寫一個filesystem,主要是讓大家對VFS的framework有一些基本認識。
首先要透過register_filesystem()註冊對應的filesystem,而register_filesystem()需要struct file_system_type的一些基本參數如下:
static struct file_system_type bv_fs_type = {
    // for internal VFS use: you should initialize this to THIS_MODULE in most cases
    .owner = THIS_MODULE,
    // the name of the filesystem type, such as "ext2", "iso9660"
    .name = "bvfs",
    // the method to call when a new instance of this filesystem should be mounted
    .mount = bvfs_mount,
    // the method to call when an instance of this filesystem should be shut down
    .kill_sb = bvfs_kill_sb,
};

MODULE_ALIAS_FS("bv");

static int __init init_bv_fs(void)
{
    printk("%s(#%d)\n", __func__, __LINE__);
    return register_filesystem(&bv_fs_type);
}
module_init(init_bv_fs);
name是filesystem type的name,mount是mount的時候會被呼叫的method,mount() method必須回傳root dentry,kill_sb則是umount會被呼叫的method。
通常mount() method會調用generic mount() implementations,包含mount_bdev、mount_nodev、mount_single並帶入fill_super() callback用於初始化struct super_block *與創建root dentry。關係圖概略如下:

在bvfs_fill_super()中主要要填入super block operations,並透過bvfs_get_inode()取得mount point/root的inode,再透過d_make_root()取得root dentry。
struct inode *bvfs_get_inode(struct super_block *sb, const struct inode *dir, umode_t mode, dev_t dev)
{
    // Allocates a new inode for given superblock.
    // The default gfp_mask for allocations related
    // to inode->i_mapping is GFP_HIGHUSER_MOVABLE.
    // If HIGHMEM pages are unsuitable or it is known
    // that pages allocated for the page cache are
    // not reclaimable or migratable, mapping_set_gfp_mask
    // must be called with suitable flags on
    // the newly created inode's mapping
    struct inode *inode = new_inode(sb);
    pr_debug("%s(#%d): mode:0%o\n", __func__, __LINE__, mode);
    if (inode) {
        inode->i_ino = get_next_ino();
        pr_debug("%s(#%d): i_ino:%lx\n", __func__, __LINE__, inode->i_ino);

        // Init uid,gid,mode for new inode according to posix standards
        inode_init_owner(inode, dir, mode);

        // add file operation
        inode->i_mapping->a_ops = &bvfs_aops;
        inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);

        switch (mode & S_IFMT) {
            default:
                init_special_inode(inode, mode, dev);
                break;

            case S_IFREG:
                inode->i_op = &bvfs_file_inode_operations;
                inode->i_fop = &bvfs_file_operations;
                break;

            case S_IFDIR:
                inode->i_op = &bvfs_dir_inode_ops;
                inode->i_fop = &simple_dir_operations;

                /* directory inodes start off with i_nlink == 2 (for "." entry) */
                inc_nlink(inode);
                break;
        }
    }
    return inode;
}

#define BVFS_MAGIC 0x20201204

/**
 * create and populate the root directory for our new filesystem.
 */
static int bvfs_fill_super(struct super_block *sb, void *data, int silent)
{
    struct bvfs_fs_info *fsi;
    struct inode *inode;

    pr_debug("%s(#%d): data:%s, silent:%d\n", __func__, __LINE__, (char *) data, silent);

    fsi = kzalloc(sizeof(*fsi), GFP_KERNEL);
    sb->s_fs_info = fsi;
    if (!fsi) {
        pr_err("%s(#%d): kzalloc()\n", __func__, __LINE__);
        return -ENOMEM;
    }
    // set default mount permission to 755
    fsi->mount_opts.mode = S_IRWXU | (S_IRGRP | S_IXGRP) | (S_IROTH | S_IXOTH);

    // The super block operations are set at the time of mounting.
    sb->s_maxbytes = MAX_LFS_FILESIZE;
    // The maximum file system block size is limited by the page cache size
    // (which is 4k on x86 systems).
    sb->s_blocksize = PAGE_SIZE;
    // The number of bits that make up the filesystem block size
    sb->s_blocksize_bits = PAGE_SHIFT;
    sb->s_magic = BVFS_MAGIC;
    sb->s_op = &bvfs_ops;
    sb->s_time_gran = 1;

    // passed S_IFDIR, the returned inode will describe a directory
    inode = bvfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
    if (!inode) {
        pr_err("%s(#%d): bvfs_get_inode failed\n", __func__, __LINE__);
        return -ENOMEM;
    }
    // This directory inode must be put into the directory cache
    // (by way of a "dentry" structure) so that the VFS can find it
    sb->s_root = d_make_root(inode);
    if (!sb->s_root) {
        pr_err("%s(#%d): d_make_root()\n", __func__, __LINE__);
        return -ENOMEM;
    }

    dump_stack();
    return 0;
}

/*
 *  @param fs_type describes the filesystem, partly initialized by the specific filesystem code
 *  @param flags mount flags
 *  @param dev_name the device name we are mounting
 *  @param data arbitrary mount options, usually comes as an ASCII string
 *
 *  @return struct dentry
 *
 */
static struct dentry *bvfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data)
{
    pr_debug("%s(#%d): flags:%d, dev_name:%s, data:%s\n", __func__, __LINE__, flags, dev_name, (char *) data);
    /*
     * Usually, a filesystem uses one of the generic mount() implementations
     * and provides a fill_super() callback instead. The generic variants are:
     *
     * mount_bdev: mount a filesystem residing on a block device
     *
     * mount_nodev: mount a filesystem that is not backed by a device
     *
     * mount_single: mount a filesystem which shares the instance between all mounts
     */
    return mount_nodev(fs_type, flags, data, bvfs_fill_super);
}

static void bvfs_kill_sb(struct super_block *sb)
{
    pr_debug("%s(#%d)\n", __func__, __LINE__);
    // kill_litter_super() is a generic function provided by the VFS;
    // it simply cleans up all of the in-core structures when the filesystem is unmounted
    kill_litter_super(sb);
}

透過簡單的mount可以更了解一下整個call flow
/ # sysctl kernel.printk='8 8 8 8'
kernel.printk = 8 8 8 8
/ # insmod bvfs.ko
[  181.936205] init_bv_fs(#425)

/ # mount bvfs mnt -t bvfs
sys_mount()
  |-> ksys_mount()
    |-> do_mount()
      |-> do_new_mount()
        |-> vfs_kern_mount()
          |-> mount_fs()
            |-> bvfs_mount()
              |-> mount_nodev()
                |-> bvfs_fill_super()
bvfs_mount(#392): flags:32768, dev_name:bvfs, data:(null)
bvfs_fill_super(#346): data:(null), silent:1
bvfs_get_inode(#302): mode:040755
bvfs_get_inode(#305): i_ino:1340
CPU: 0 PID: 810 Comm: mount Tainted: G           O      4.19.0-rc8+ #32
Hardware name: ARM-Versatile Express
[<80111eb4>] (unwind_backtrace) from [<8010d8e4>] (show_stack+0x10/0x14)
[<8010d8e4>] (show_stack) from [<806c81b0>] (dump_stack+0x88/0x9c)
[<806c81b0>] (dump_stack) from [<7f000b34>] (bvfs_fill_super+0x108/0x124 [bvfs])
[<7f000b34>] (bvfs_fill_super [bvfs]) from [<8024d8ec>] (mount_nodev+0x44/0x90)
[<8024d8ec>] (mount_nodev) from [<7f000468>] (bvfs_mount+0x68/0x78 [bvfs])
[<7f000468>] (bvfs_mount [bvfs]) from [<8024e3f0>] (mount_fs+0x14/0xa8)
[<8024e3f0>] (mount_fs) from [<8026af6c>] (vfs_kern_mount.part.3+0x48/0xf8)
[<8026af6c>] (vfs_kern_mount.part.3) from [<8026d80c>] (do_mount+0x57c/0xc80)
[<8026d80c>] (do_mount) from [<8026e2a4>] (ksys_mount+0x8c/0xb4)
[<8026e2a4>] (ksys_mount) from [<80101000>] (ret_fast_syscall+0x0/0x54)
Exception stack(0x8550dfa8 to 0x8550dff0)
dfa0:                   00000000 00000000 7e9d8f85 7e9d8f8a 7e9d8f91 00008000
dfc0: 00000000 00000000 7e9d8f85 00000015 7e9d8f91 00008000 00000000 00000000
dfe0: 00000000 7e9d8b68 0007e5a7 00012fba


/ # cd mnt
sys_stat64()
  |-> vfs_statx()
    |-> filename_lookup()
      |-> walk_component()
        |-> lookup_slow()
          |-> __lookup_slow()
            |-> bvfs_lookup()

bvfs_lookup(#218):
CPU: 0 PID: 800 Comm: sh Tainted: G           O      4.19.0-rc8+ #32
Hardware name: ARM-Versatile Express
[<80111eb4>] (unwind_backtrace) from [<8010d8e4>] (show_stack+0x10/0x14)
[<8010d8e4>] (show_stack) from [<806c81b0>] (dump_stack+0x88/0x9c)
[<806c81b0>] (dump_stack) from [<7f000224>] (bvfs_lookup+0x40/0x5c [bvfs])
[<7f000224>] (bvfs_lookup [bvfs]) from [<802551a0>] (__lookup_slow+0x8c/0x154)
[<802551a0>] (__lookup_slow) from [<80255298>] (lookup_slow+0x30/0x44)
[<80255298>] (lookup_slow) from [<802579c8>] (walk_component+0x1c8/0x300)
[<802579c8>] (walk_component) from [<802580c8>] (path_lookupat+0x70/0x1fc)
[<802580c8>] (path_lookupat) from [<80259da8>] (filename_lookup+0x9c/0x10c)
[<80259da8>] (filename_lookup) from [<8024f3d0>] (vfs_statx+0x68/0xd0)
[<8024f3d0>] (vfs_statx) from [<8024fbc8>] (sys_stat64+0x38/0x68)
[<8024fbc8>] (sys_stat64) from [<80101000>] (ret_fast_syscall+0x0/0x54)
Exception stack(0x85505fa8 to 0x85505ff0)
5fa0:                   0018a058 00000000 0018a25c 7e97caf8 7e97caf8 0018a266
5fc0: 0018a058 00000000 00000001 000000c3 001864dc 00186478 00000000 7e97cadc
5fe0: 000000c3 7e97cac4 000ddb9b 00011726


基本上dentry就是一個representation/cache結構,用於反應file-system的目錄結構,而不須真的讀從file-system讀出資料。
A "dentry" in the Linux kernel is the in-memory representation of a directory entry;
it is a way of remembering the resolution of a given file or directory name 
without having to search through the filesystem to find it. 
The dentry cache speeds lookups considerably; 
keeping dentries for frequently accessed names like /tmp, /dev/null, 
or /usr/bin/tetris saves a lot of filesystem I/O.


概略的一些關係圖如下,希望能讓大家能對這些關係有較清楚的概念。



目前為止的功能只能達到mount,其餘的下一個章節繼續囉。


2020年10月9日 星期五

How MSM Parse Partition Table in Kernel


call flow
msm_nand_probe()
  |-> msm_nand_parse_smem_ptable() 
    |-> smem_get_entry(SMEM_AARM_PARTITION_TABLE)
  |-> mtd_device_parse_register()


code snippet, https://android.googlesource.com/kernel/msm/+/android-msm-hammerhead-3.4-kk-r1/drivers/mtd/devices/msm_qpic_nand.c
#define FLASH_PART_MAGIC1        0x55EE73AA
#define FLASH_PART_MAGIC2        0xE35EBDDB
#define FLASH_PTABLE_V3         3
#define FLASH_PTABLE_V4         4
#define FLASH_PTABLE_MAX_PARTS_V3  16
#define FLASH_PTABLE_MAX_PARTS_V4  32
#define FLASH_PTABLE_HDR_LEN     (4*sizeof(uint32_t))

// Parititon Table Store in Flash.
struct flash_partition_entry {
  char name[FLASH_PTABLE_ENTRY_NAME_SIZE];
  u32 offset;   /* Offset in blocks from beginning of device */
  u32 length;   /* Length of the partition in blocks */
  u8 attr;     /* Flags for this partition */
};

struct flash_partition_table {
  u32 magic1;
  u32 magic2;
  u32 version;
  u32 numparts;
  struct flash_partition_entry part_entry[FLASH_PTABLE_MAX_PARTS_V4];
};

static struct mtd_partition mtd_part[FLASH_PTABLE_MAX_PARTS_V4];

static int __devinit msm_nand_probe(struct platform_device *pdev)
{
  ...
  err = msm_nand_parse_smem_ptable(&nr_parts);
  ...
  for (i = 0; i < nr_parts; i++) {
    mtd_part[i].offset *= info->mtd.erasesize;
    mtd_part[i].size *= info->mtd.erasesize;
  }
  err = mtd_device_parse_register(&info->mtd, NULL, NULL, &mtd_part[0], nr_parts);
  ...
}

// Get Partition Table from RAM/Flash via smem_get_entry(SMEM_AARM_PARTITION_TABLE).
static int msm_nand_parse_smem_ptable(int *nr_parts)
{
  uint32_t  i, j;
  uint32_t len = FLASH_PTABLE_HDR_LEN;
  struct flash_partition_entry *pentry;
  char *delimiter = ":";
  pr_info("Parsing partition table info from SMEM\n");
  /* Read only the header portion of ptable */
  ptable = *(struct flash_partition_table *)
            (smem_get_entry(SMEM_AARM_PARTITION_TABLE, &len));
  /* Verify ptable magic */
  if (ptable.magic1 != FLASH_PART_MAGIC1 || 
      ptable.magic2 != FLASH_PART_MAGIC2) {
    pr_err("Partition table magic verification failed\n");
    goto out;
  }
  /* Ensure that # of partitions is less than the max we have allocated */
  if (ptable.numparts > FLASH_PTABLE_MAX_PARTS_V4) {
    pr_err("Partition numbers exceed the max limit\n");
    goto out;
  }
  /* Find out length of partition data based on table version. */
  if (ptable.version <= FLASH_PTABLE_V3) {
    len = FLASH_PTABLE_HDR_LEN + FLASH_PTABLE_MAX_PARTS_V3 *
        sizeof(struct flash_partition_entry);
  } else if (ptable.version == FLASH_PTABLE_V4) {
    len = FLASH_PTABLE_HDR_LEN + FLASH_PTABLE_MAX_PARTS_V4 *
        sizeof(struct flash_partition_entry);
  } else {
    pr_err("Unknown ptable version (%d)", ptable.version);
    goto out;
  }
  *nr_parts = ptable.numparts;
  ptable = *(struct flash_partition_table *)
        (smem_get_entry(SMEM_AARM_PARTITION_TABLE, &len));
  for (i = 0; i < ptable.numparts; i++) {
    pentry = &ptable.part_entry[i];
    if (pentry->name == '\0')
      continue;
    /* Convert name to lower case and discard the initial chars */
    mtd_part[i].name = pentry->name;
    for (j = 0; j < strlen(mtd_part[i].name); j++)
      *(mtd_part[i].name + j) = tolower(*(mtd_part[i].name + j));
      strsep(&(mtd_part[i].name), delimiter);
      mtd_part[i].offset      = pentry->offset;
      mtd_part[i].mask_flags  = pentry->attr;
      mtd_part[i].size        = pentry->length;
      pr_debug("%d: %s offs=0x%08x size=0x%08x attr:0x%08x\n",
          i, pentry->name, pentry->offset, pentry->length, pentry->attr);
  }
  pr_info("SMEM partition table found: ver: %d len: %d\n",
      ptable.version, ptable.numparts);
  return 0;
out:
  return -EINVAL;
}

static const struct of_device_id msm_nand_match_table[] = {
  { .compatible = "qcom,msm-nand", },
  {},
};


2020年9月27日 星期日

Linux Kernel(10.3.1)- Command line partition table parsing for Kernel 4.19


由於10.3的kernel過時,所以10.3.1是用kernel 4.19來做補充。
MTD Partition除了在code中寫死以外,其實還可以透過一些parsers來作規劃,這一章就要來教大家如何使用"Command line partition table parsing"。
首先必須在kernel中啟用"Command line partition table parsing",請參照10.3。
接著在command line中加入mtdparts的設定,其簡單說明如下:
 * mtdparts=<mtddef>[;<mtddef]
 * <mtddef>  := <mtd-id>:<partdef>[,<partdef>]
 * <partdef> := <size>[@<offset>][<name>][ro][lk]
 * <mtd-id>  := unique name used in mapping driver/device (mtd->name)
 * <size>    := standard linux memsize OR "-" to denote all remaining space
 *              size is automatically truncated at end of device
 *              if specified or truncated size is 0 the part is skipped
 * <offset>  := standard linux memsize
 *              if omitted the part will immediately follow the previous part
 *              or 0 if the first part
 * <name>    := '(' NAME ')'
 *              NAME will appear in /proc/mtd
 *
 * <size> and <offset> can be specified such that the parts are out of order
 * in physical memory and may even overlap.
 *
 * The parts are assigned MTD numbers in the order they are specified in the
 * command line regardless of their order in physical memory.

 * Due to the way Linux handles the command line, no spaces are
 * allowed in the partition definition, including mtd id's and partition
 * names.

這裡我用qemu與mtdram來執行,執行參數如下
qemu-system-arm -s -M vexpress-a9 -m 128M -kernel ./linux/arch/arm/boot/zImage \
                -dtb ./linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -initrd ./initrd-arm.img \
                -nographic -append "console=ttyAMA0 mtdparts=\"mtdram test device:1024k(p1),1024k(p2),-(p3)\""

這裡特別要說明的是mtd-id就是mtd name,基本上你可以以cat /proc/mtd,就可以知道mtd name了,或者trace一下code也行
// file "drivers/mtd/cmdlinepart.c"
static int parse_cmdline_partitions(struct mtd_info *master,
                    const struct mtd_partition **pparts,
                    struct mtd_part_parser_data *data)
{
    ...
const char *mtd_id = master->name;
    /*
     * Search for the partition definition matching master->name.
     * If master->name is not set, stop at first partition definition.
     */
    for (part = partitions; part; part = part->next) {
        if ((!mtd_id) || (!strcmp(part->mtd_id, mtd_id)))
            break;
    }
    ...
}

// drivers/mtd/devices/mtdram.c
static int __init init_mtdram(void)
{
    ...
    err = mtdram_init_device(mtd_info, addr, MTDRAM_TOTAL_SIZE, "mtdram test device");
    ...
}

int mtdram_init_device(struct mtd_info *mtd, void *mapped_address,
        unsigned long size, const char *name)
{
    ...
    /* Setup the MTD structure */
    mtd->name = name;
    ...
}

此外我多印了一些deubg資訊在drivers/mtd/cmdlinepart.c
diff --git a/drivers/mtd/cmdlinepart.c b/drivers/mtd/cmdlinepart.c
index 3ea44cff9b75..a411ce85097e 100644
--- a/drivers/mtd/cmdlinepart.c
+++ b/drivers/mtd/cmdlinepart.c
@@ -48,6 +48,7 @@
  * edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home)
  */

+#define DEBGU
 #define pr_fmt(fmt)    "mtd: " fmt

 #include 
@@ -58,7 +59,7 @@
 #include 

 /* debug macro */
-#if 0
+#if 1
 #define dbg(x) do { printk("DEBUG-CMDLINE-PART: "); printk x; } while(0)
 #else
 #define dbg(x)
@@ -315,6 +316,7 @@ static int parse_cmdline_partitions(struct mtd_info *master,
        struct cmdline_mtd_partition *part;
        const char *mtd_id = master->name;

+       printk("BROOK, %s(#%d), cmdline_parsed:%d\n", __func__, __LINE__, cmdline_parsed);
        /* parse command line */
        if (!cmdline_parsed) {
                err = mtdpart_setup_real(cmdline);
@@ -327,12 +329,15 @@ static int parse_cmdline_partitions(struct mtd_info *master,
         * If master->name is not set, stop at first partition definition.
         */
        for (part = partitions; part; part = part->next) {
+           printk("BROOK, %s(#%d), mtd_id:%s, part->mtd_id:%s\n", __func__, __LINE__, mtd_id, part->mtd_id);
                if ((!mtd_id) || (!strcmp(part->mtd_id, mtd_id)))
                        break;
        }

-       if (!part)
+       printk("BROOK, %s(#%d), part:%p\n", __func__, __LINE__, part);
+       if (!part) {
                return 0;
+       }

        for (i = 0, offset = 0; i < part->num_parts; i++) {
                if (part->parts[i].offset == OFFSET_CONTINUOUS)

執行結果如下:
/ # dmesg
...
[    4.688086] BROOK, parse_cmdline_partitions(#319), cmdline_parsed:0
[    4.688876] DEBUG-CMDLINE-PART:
[    4.688994] parsing <1024k(p1),1024k(p2),-(p3)>
[    4.690787] DEBUG-CMDLINE-PART:
[    4.690944] partition 2: name <p3>, offset ffffffffffffffff, size ffffffffffffffff, mask flags 0
[    4.692017] DEBUG-CMDLINE-PART:
[    4.692102] partition 1: name <p2>, offset ffffffffffffffff, size 100000, mask flags 0
[    4.692950] DEBUG-CMDLINE-PART:
[    4.693034] partition 0: name <p1>, offset ffffffffffffffff, size 100000, mask flags 0
[    4.693993] DEBUG-CMDLINE-PART:
[    4.694101] mtdid=<mtdram test device> num_parts=<3>
[    4.695000] BROOK, parse_cmdline_partitions(#332), mtd_id:40000000.flash, part->mtd_id:mtdram test device
[    4.696546] BROOK, parse_cmdline_partitions(#337), part:  (null)
[    4.763129] BROOK, parse_cmdline_partitions(#319), cmdline_parsed:1
[    4.763742] BROOK, parse_cmdline_partitions(#332), mtd_id:48000000.psram, part->mtd_id:mtdram test device
[    4.764461] BROOK, parse_cmdline_partitions(#337), part:  (null)
[    4.806551] BROOK, parse_cmdline_partitions(#319), cmdline_parsed:1
[    4.808150] BROOK, parse_cmdline_partitions(#332), mtd_id:mtdram test device, part->mtd_id:mtdram test device
[    4.809227] BROOK, parse_cmdline_partitions(#337), part:(ptrval)
[    4.810325] 3 cmdlinepart partitions found on MTD device mtdram test device
[    4.811006] Creating 3 MTD partitions on "mtdram test device":
[    4.813103] 0x000000000000-0x000000100000 : "p1"
[    4.830841] 0x000000100000-0x000000200000 : "p2"
[    4.846502] 0x000000200000-0x000000400000 : "p3"
...

/ # cat /proc/mtd
dev:    size   erasesize  name
mtd0: 08000000 00040000 "40000000.flash"
mtd1: 02000000 00001000 "48000000.psram"
mtd2: 00100000 00020000 "p1"
mtd3: 00100000 00020000 "p2"
mtd4: 00200000 00020000 "p3"

/ # zcat /proc/config.gz | grep -i MTDRAM
CONFIG_MTD_MTDRAM=y
CONFIG_MTDRAM_TOTAL_SIZE=4096
CONFIG_MTDRAM_ERASE_SIZE=128




2020年9月12日 星期六

LP5560 - Single-LED Driver With Single-Wire Control


這是一顆我們俗稱的呼吸燈,因為預設的行為就像呼吸一樣,由滅到緩慢至亮,再由亮緩慢至滅。可以透過Single-Wire控制其行為,作法大同小異。這裡就稍微筆記一下。

6.6 Single-Wire Interface Timing Requirements

這裡描述了每個階段的high/low以及min/max時間,比如TC_ON > 20us,TC_OF > 30us之類的,簡略的code如下
#define SHORT_DELAY 10
#define TC_ON (20 + SHORT_DELAY)
#define TC_OFF (30 + SHORT_DELAY)
#define T_ENTER (500 + 10*SHORT_DELAY)
#define T_BLANK (1500 - 500)

// T-ENTER 
GPIO(CTRL_PIN, high);
us_delay(TC_ON);
GPIO(CTRL_PIN, low);
us_delay(TC_OFF);

GPIO(CTRL_PIN, high);
us_delay(TC_ON);
GPIO(CTRL_PIN, low);
us_delay(TC_OFF + T_ENTER + T_BLANK); // over T-BLANK period


7.3.3.4 Entering Follow Mode

這裡描述Entering Follow Mode的指令結構,基本上就是,"Training start command" + "Blank Period" + "Follow Mode command" + "Training end command"
#define SHORT_DELAY 10
#define TC_ON (20 + SHORT_DELAY)
#define TC_OFF (30 + SHORT_DELAY)
#define T_ENTER (500 + SHORT_DELAY)
#define T_BLANK (1500 - 500)
#define TCAL (350 + 10 * SHORT_DELAY))
#define TT_OFF (200 + 10 * SHORT_DELAY)
#define TIMEOUT (127 * TCAL)

static void training_start_command(void)
{
  int i = 0;
  while (i++ < 2) {
    GPIO(CTRL_PIN, high);
    us_delay(TC_ON);
    GPIO(CTRL_PIN, low);
    us_delay(TC_OFF);
  }
  us_delay(T_ENTER + T_BLANK); // over T-BLANK period due to 
}

static void follow_mode(uint32_t i, uint32_t r1, uint32_t on1, uint32_t f1, uint32_t off1)
{
  // C
  GPIO(CTRL_PIN, high);
  us_delay(TCAL);
  GPIO(CTRL_PIN, low);
  us_delay(TT_OFF);

  // I
  GPIO(CTRL_PIN, high);
  us_delay(i * TCAL);
  GPIO(CTRL_PIN, low);
  us_delay(TT_OFF);

  // r1
  GPIO(CTRL_PIN, high);
  us_delay(r1 * TCAL);
  GPIO(CTRL_PIN, low);
  us_delay(TT_OFF);

  // on1
  GPIO(CTRL_PIN, high);
  us_delay(on1 * TCAL);
  GPIO(CTRL_PIN, low);
  us_delay(TT_OFF);

  // off1
  GPIO(CTRL_PIN, high);
  us_delay(off1 * TCAL);
  GPIO(CTRL_PIN, low);
  us_delay(TT_OFF);
}

static void training_start_command(void)
{
  int i = 0;
  while (i++ < 3) {
    GPIO(CTRL_PIN, high);
    us_delay(TC_ON);
    GPIO(CTRL_PIN, low);
    us_delay(TC_OFF);
  }
  // more than 127 × TCAL time this is interpreted as timeout.
  us_delay(TIMEOUT );
}





2020年9月6日 星期日

Linux Kernel(20.3)- Creating an input device driver


這個範例用最簡單的input device driver來解釋必要的部分,首先用input_allocate_device()取得"struct input_dev",接著填入必要欄位,如evbit,這個範例是只會產生EV_KEY event type,且key/event code只有KEY_UP(103)與KEY_DOWN(108),之後呼叫input_register_device(btn_dev)向系統註冊一個input device。
我用QEMU跑,沒有實際的硬體可以驅動該input device,所以我在sys底下創建一個sysfs_report_key讓input device送event。
#include <linux/module.h>
#include <linux/init.h>

#include <linux/input.h>
#include <linux/device.h>
#include <linux/sysfs.h>

static struct input_dev *btn_dev;

static ssize_t sysfs_report_key_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t n)
{
  int key, value;
  if (sscanf(buf, "%d %d", &key, &value) != 2) {
    printk("invalid format:%s", buf);
    return n;
  }
  printk("key:%d, value:%d", key, value);
  input_report_key(btn_dev, key, value);
  input_sync(btn_dev);
  return n;
}

static DEVICE_ATTR_WO(sysfs_report_key);
static int __init button_init(void)
{
  int ret;

  btn_dev = input_allocate_device();
  if (btn_dev == NULL) {
    printk(KERN_ERR "Not enough memory\n");
    return -ENOMEM;
  }

  btn_dev->name = "brook-input-dev";
  btn_dev->evbit[0] = BIT(EV_KEY);
  set_bit(KEY_UP, btn_dev->keybit);
  set_bit(KEY_DOWN, btn_dev->keybit);

  ret = input_register_device(btn_dev);
  if (ret) {
    dev_err(&(btn_dev->dev), "Failed to register device\n");
    goto err_free_dev;
  }

  /* used for send event from user-space. please ignore it */
  ret = device_create_file(&(btn_dev->dev), &dev_attr_sysfs_report_key);
  if (ret) {
    dev_err(&(btn_dev->dev), "cannot create sysfs attribute\n");
    goto err_unreg_dev;
  }

  return 0;

err_unreg_dev:
  input_unregister_device(btn_dev);
err_free_dev:
  input_free_device(btn_dev);
  return ret;
}

static void __exit button_exit(void)
{
  input_unregister_device(btn_dev);
  input_free_device(btn_dev);
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");


執行結果
/ # insmod inputdev.ko
[60145.219561] inputdev: loading out-of-tree module taints kernel.
[60145.277803] input: brook-input-dev as /devices/virtual/input/input3
/ # ./input_test /dev/input/event1 &

送出KEY_UP(103)後,input_test會收到event。
/ # echo '103 1' > /sys/devices/virtual/input/input3/sysfs_report_key
type:1, code:103, val:1
type:0, code:0, val:0

重複的送出KEY_UP(103),input_test不會收到event。
/ # echo '103 1' > /sys/devices/virtual/input/input3/sysfs_report_key
[60174.829756] key:103, value:1
/ # echo '103 0' > /sys/devices/virtual/input/input3/sysfs_report_key
[60182.965543] key:103, value:1
type:1, code:103, val:0
/ # type:0, code:0, val:0

Key value只有0/1。
/ # echo '103 2' > /sys/devices/virtual/input/input3/sysfs_report_key
[60189.286751] key:103, value:0
type:1, code:103, val:1
/ # type:0, code:0, val:0

其他的key/event code是不會被送出。
/ # echo '105 2' > /sys/devices/virtual/input/input3/sysfs_report_key
[60194.705307] key:103, value:2
/ # echo '108 2' > /sys/devices/virtual/input/input3/sysfs_report_key
[60201.887535] key:105, value:2
type:1, code:108, val:1
type:0, code:0, val:0


基本上,所有的input_report_xxx()/input_sync底層都是呼叫input_event,input_report_key的value只有0/1。
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
  input_event(dev, EV_KEY, code, !!value);
}

static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
{
  input_event(dev, EV_REL, code, value);
}

static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
  input_event(dev, EV_ABS, code, value);
}

static inline void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
{
  input_event(dev, EV_FF_STATUS, code, value);
}

static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value)
{
  input_event(dev, EV_SW, code, !!value);
}

static inline void input_sync(struct input_dev *dev)
{
  input_event(dev, EV_SYN, SYN_REPORT, 0);
}


input_event() 在送出key/event code之前,會先判斷送的event type與當初device設定的是否一致。
void input_event(struct input_dev *dev,
         unsigned int type, unsigned int code, int value)
{
  unsigned long flags;

  if (is_event_supported(type, dev->evbit, EV_MAX)) {
    spin_lock_irqsave(&dev->event_lock, flags);
    input_handle_event(dev, type, code, value);
    spin_unlock_irqrestore(&dev->event_lock, flags);
  }
}

重複的KEY value是不會被設成INPUT_PASS_TO_HANDLERS,也就是INPUT_IGNORE_EVENT。
static void input_handle_event(struct input_dev *dev,
                   unsigned int type, unsigned int code, int value)
{
  int disposition = input_get_disposition(dev, type, code, &value);

  ...
}

static int input_get_disposition(struct input_dev *dev,
              unsigned int type, unsigned int code, int *pval)
{
  int disposition = INPUT_IGNORE_EVENT;
  int value = *pval;

  switch (type) {
  ...
  case EV_KEY:
    if (is_event_supported(code, dev->keybit, KEY_MAX)) {
      /* auto-repeat bypasses state updates */
      if (value == 2) {
        disposition = INPUT_PASS_TO_HANDLERS;
        break;
      }

      if (!!test_bit(code, dev->key) != !!value) {
        __change_bit(code, dev->key);
        disposition = INPUT_PASS_TO_HANDLERS;
      }

    }
    break;
  ...
  }
  
  *pval = value;
  return disposition;
}


    參考資料:
  • Documentation/input/input-programming.rst



2020年9月5日 星期六

Linux Kernel(20.2)- uinput module


uinput是kernel module透過寫入/dev/uinput從userpace模擬input device裝置,並且驅動特定的event。
其kernel configuration的path為Input device support -> Miscellaneous devices -> User level driver support
設定簡單,可以參考以下範例大概就知道如何操控了。
#include <stdio.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <unistd.h>

#include <string.h>

#include <linux/input.h>

#include <linux/uinput.h>

void emit(int fd, int type, int code, int val)
{
  struct input_event ie;

  ie.type = type;
  ie.code = code;
  ie.value = val;
  /* timestamp values below are ignored */
  ie.time.tv_sec = 0;
  ie.time.tv_usec = 0;

  write(fd, &ie, sizeof(ie));
}

int main(void)
{
  struct uinput_setup usetup;

  int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);

  /*
   * The ioctls below will enable the device that is about to be
   * created, to pass key events, in this case the space key.
   **/
  ioctl(fd, UI_SET_EVBIT, EV_KEY);
  ioctl(fd, UI_SET_KEYBIT, KEY_SPACE);

  memset(&usetup, 0, sizeof(usetup));
  usetup.id.bustype = BUS_USB;
  usetup.id.vendor = 0x1234;    /* sample vendor */
  usetup.id.product = 0x5678;   /* sample product */
  strcpy(usetup.name, "Example device");

  ioctl(fd, UI_DEV_SETUP, &usetup);
  ioctl(fd, UI_DEV_CREATE);

  /*
   * On UI_DEV_CREATE the kernel will create the device node for this
   * device. We are inserting a pause here so that userspace has time
   * to detect, initialize the new device, and can start listening to
   * the event, otherwise it will not notice the event we are about
   * to send. This pause is only needed in our example code!
   **/
  printf("will report the event after 10 sec\n");
  sleep(10);

  /* Key press, report the event, send key release, and report again */
  emit(fd, EV_KEY, KEY_SPACE, 1);
  emit(fd, EV_SYN, SYN_REPORT, 0);
  emit(fd, EV_KEY, KEY_SPACE, 0);
  emit(fd, EV_SYN, SYN_REPORT, 0);

  /*
   * Give userspace some time to read the events before we destroy the
   * device with UI_DEV_DESTOY.
   **/
  sleep(1);

  ioctl(fd, UI_DEV_DESTROY);
  close(fd);

  return 0;
}


執行結果,input_test為先前章節的user space程式,uinput為該章節範例,執行uinput之後,會在/dev/input/長出對應的device node。
/ # zcat /proc/config.gz | grep UINPUT
CONFIG_INPUT_UINPUT=y
/ # ./uinput &
/ # [   99.433163] input: Example device as /devices/virtual/input/input4
/ # ls /dev/input/
event0  event1
/ # ./input_test /dev/input/event1
will report the event after 10 sec
type:1, code:57, val:1
type:0, code:0, val:0
type:1, code:57, val:0
type:0, code:0, val:0


    參考資料:
  • Documentation/input/uinput.rst



Linux Kernel(20.1)- Input device user program


這裡簡單的描述一下如何寫一個user program去讀去input device data,基本上,就是直接open /dev/inputN,然後將data mapping成struct input_event。
struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};
type: event type
code: key code for EV_KEY
摘入一段/usr/include/linux/input-event-codes.h
/*
 * Event types
 */
#define EV_SYN          0x00
#define EV_KEY          0x01
#define EV_REL          0x02
#define EV_ABS          0x03
#define EV_MSC          0x04
#define EV_SW           0x05
#define EV_LED          0x11
#define EV_SND          0x12
#define EV_REP          0x14
#define EV_FF           0x15
#define EV_PWR          0x16
#define EV_FF_STATUS        0x17
#define EV_MAX          0x1f
#define EV_CNT          (EV_MAX+1)

/*
 * Key code
 */
#define KEY_RESERVED        0
#define KEY_ESC         1
#define KEY_1           2
#define KEY_2           3
...

#define BTN_MISC        0x100
#define BTN_0           0x100
#define BTN_1           0x101
#define BTN_2           0x102
...

#define KEY_OK          0x160
#define KEY_SELECT      0x161
...


#include <stdio.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <unistd.h>

#include <string.h>

#include <linux/input.h>

int main(int argc, char *argv[])
{
  struct input_event evt;
  int fd;
  fd = open(argv[1], O_RDONLY);
  if (fd < 0) {
    printf("open %s failed: %s/%d\n", argv[0], strerror(errno), errno);
    return -1;
  }

  while (1) {
    if (sizeof(evt) == read(fd, &evt, sizeof(evt))) {
      printf("type:%d, code:%d, val:%d\n", evt.type, evt.code, evt.value);
    } else {
      printf("read failed: %s/%d\n", strerror(errno), errno);
    }
  }
  return 0;
}


    參考資料:
  • Documentation/input/event-codes.rst



Citizens Broadband Radio Service (CBRS)


全球都將3.5GHz頻段的頻譜視為新一代電信服務的理想選擇,因為它既具備長距離傳輸的優勢,又有大量且連續的可用頻寬,包括中國、歐洲與南韓都已將它作為5G之用,台灣自去年底展開的5G頻譜競標,不只在今年初創下1,380億元新台幣的總標金紀錄,且最熱門的3.5GHz頻段的頻譜,總標金即達1364.33億元。

在FCC的3.5 GHz CBRS頻段釋出計畫中,引進了三層式頻譜接取架構。在此架構下,當Incumbents 與 PALs未使用該頻段時,GAAs皆可以使用。而當有很多GAAs處於同一場所時,要按照公平的原則使用可用頻率。因此,FCC將以SAS(Spectrum Access System),實現不同用戶及應用之間的頻譜共享。

Incumbents: 指的是那些原本在3.5G運作的使用者, 如 fixed satellite service, US Navy Radar等。
PALs(Priority Access Licenses): 指的是那些標下這個頻段使用者, 如service providers。
GAAs(General Authorized Access): 其他人。






2020年8月29日 星期六

PDN CONNECTIVITY REJECT Code


By 3GPP TS 24.244 version 16.0.0 Release 16
The cause IE typically indicates one of the following cause values:
  #8: operator determined barring;
  #26: insufficient resources;
  #27: missing or unknown APN;
  #28: unknown PDN type;
  #29: user authentication failed;
  #30: request rejected by PDN GW;
  #31: request rejected, unspecified;
  #32: service option not supported;
  #33: requested service option not subscribed;
  #34: service option temporarily out of order;
  #35: PTI already in use;
  #38: network failure;
  #50: PDN type IPv4 only allowed;
  #51: PDN type IPv6 only allowed;
  #52: single address bearers only allowed;
  #54: PDN connection does not exist;
  #55: multiple PDN connections for a given APN not allowed;
  #95 – 111: protocol errors;
  #113: Multiple accesses to a PDN connection not allowed; 




2020年6月27日 星期六

Note for Yocto Project Development Tasks Manual v3.1 - CH3.2. Customizing Images


Yocto 可以讓User自訂image以滿足特定需求, 這個章節就是在介紹如何自訂image.
最簡單的image.bb就是全部空白
images/machine-image-brook.bb
inherit image
IMAGE_FEATURES = ""
LICENSE = "MIT"
IMAGE_LINGUAS = ""
build結果
brook@vista:~/apps_proc/poky/build/tmp-glibc/work/oe-linux-gnueabi/machine-image-brook$ find 1.0-r0/rootfs/
1.0-r0/rootfs/
1.0-r0/rootfs/var
1.0-r0/rootfs/var/cache
1.0-r0/rootfs/var/cache/opkg
1.0-r0/rootfs/var/lib
1.0-r0/rootfs/var/lib/opkg
1.0-r0/rootfs/lib
1.0-r0/rootfs/lib/systemd
1.0-r0/rootfs/lib/systemd/system
1.0-r0/rootfs/lib/systemd/system/run-postinsts.service
1.0-r0/rootfs/etc
1.0-r0/rootfs/etc/timestamp
1.0-r0/rootfs/etc/version
1.0-r0/rootfs/etc/systemd
1.0-r0/rootfs/etc/systemd/system
1.0-r0/rootfs/etc/systemd/system/sysinit.target.wants
1.0-r0/rootfs/etc/systemd/system/sysinit.target.wants/run-postinsts.service
1.0-r0/rootfs/run
1.0-r0/rootfs/usr
1.0-r0/rootfs/usr/sbin
1.0-r0/rootfs/usr/sbin/run-postinsts


3.2.1. Customizing Images Using local.conf

最簡單的客製化image就是在local.conf中加入一個package, 雖然彈性有限, 但相對簡單. 比如在local.conf加入一行
     IMAGE_INSTALL_append = " strace"
記住, strace前面有一個空白, 爾且是必須的. 此外, 善用+=運算符號可以增加可讀性.

images/machine-image-brook.bb
inherit image
IMAGE_FEATURES = ""
LICENSE = "MIT"
IMAGE_LINGUAS = ""

build結果
brook@vista:~/apps_proc/poky/build/tmp-glibc/work/oe-linux-gnueabi/machine-image-brook$ find 1.0-r0/rootfs/
1.0-r0/rootfs/
1.0-r0/rootfs/var
1.0-r0/rootfs/var/cache
1.0-r0/rootfs/var/cache/opkg
1.0-r0/rootfs/var/lib
1.0-r0/rootfs/var/lib/opkg
1.0-r0/rootfs/lib
1.0-r0/rootfs/lib/libnss_compat.so.2
1.0-r0/rootfs/lib/libresolv.so.2
1.0-r0/rootfs/lib/libresolv-2.28.so
1.0-r0/rootfs/lib/libnss_files.so.2
1.0-r0/rootfs/lib/libm.so.6
1.0-r0/rootfs/lib/libBrokenLocale-2.28.so
1.0-r0/rootfs/lib/libc-2.28.so
1.0-r0/rootfs/lib/libanl.so.1
1.0-r0/rootfs/lib/libutil.so.1
1.0-r0/rootfs/lib/libc.so.6
1.0-r0/rootfs/lib/libutil-2.28.so
1.0-r0/rootfs/lib/libnss_dns-2.28.so
1.0-r0/rootfs/lib/libnsl.so.1
1.0-r0/rootfs/lib/libdl-2.28.so
1.0-r0/rootfs/lib/libnss_compat-2.28.so
1.0-r0/rootfs/lib/ld-linux-armhf.so.3
1.0-r0/rootfs/lib/libnsl-2.28.so
1.0-r0/rootfs/lib/libpthread-2.28.so
1.0-r0/rootfs/lib/libnss_files-2.28.so
1.0-r0/rootfs/lib/libm-2.28.so
1.0-r0/rootfs/lib/libnss_dns.so.2
1.0-r0/rootfs/lib/librt-2.28.so
1.0-r0/rootfs/lib/libpthread.so.0
1.0-r0/rootfs/lib/libBrokenLocale.so.1
1.0-r0/rootfs/lib/libdl.so.2
1.0-r0/rootfs/lib/systemd
1.0-r0/rootfs/lib/systemd/system
1.0-r0/rootfs/lib/systemd/system/run-postinsts.service
1.0-r0/rootfs/lib/libanl-2.28.so
1.0-r0/rootfs/lib/ld-2.28.so
1.0-r0/rootfs/lib/librt.so.1
1.0-r0/rootfs/sbin
1.0-r0/rootfs/sbin/ldconfig
1.0-r0/rootfs/etc
1.0-r0/rootfs/etc/timestamp
1.0-r0/rootfs/etc/version
1.0-r0/rootfs/etc/ld.so.conf
1.0-r0/rootfs/etc/systemd
1.0-r0/rootfs/etc/systemd/system
1.0-r0/rootfs/etc/systemd/system/sysinit.target.wants
1.0-r0/rootfs/etc/systemd/system/sysinit.target.wants/run-postinsts.service
1.0-r0/rootfs/run
1.0-r0/rootfs/usr
1.0-r0/rootfs/usr/sbin
1.0-r0/rootfs/usr/sbin/run-postinsts
1.0-r0/rootfs/usr/bin
1.0-r0/rootfs/usr/bin/strace
1.0-r0/rootfs/usr/bin/strace-log-merge


3.2.2. Customizing Images Using Custom IMAGE_FEATURES and EXTRA_IMAGE_FEATURES

另一個方式是設定IMAGE_FEATURES與EXTRA_IMAGE_FEATURES, 這兩個功能相似, IMAGE_FEATURES是在recipe使用, 而EXTRA_IMAGE_FEATURES是在local.conf 使用. 可以參考meta/classes/core-image.bbclass了解更多細節
簡單來說, IMAGE_FEATURES 會被轉成是當的packages或configurations後加入IMAGE_INSTALL變數中. 而EXTRA_IMAGE_FEATURES變數會在bitbake.conf中, 將其加入IMAGE_FEATURES內.
EXTRA_IMAGE_FEATURES ??= ""
IMAGE_FEATURES += "${EXTRA_IMAGE_FEATURES}"


3.2.3. Customizing Images Using Custom .bb Files

你也可以透過recipe (.bb)自訂image內容, images/machine-image-brook.bb
inherit image
IMAGE_INSTALL = "strace"
IMAGE_FEATURES = ""
LICENSE = "MIT"
IMAGE_LINGUAS = ""
build結果與3.2.1.結果相同

3.2.4. Customizing Images Using Custom Package Groups

複雜一點的image,可以透過創建package group recipe來客製化image, 比如在meta/recipes-core/packagegroups/創建packagegroup-image-brook.bb, 內容如下
SUMMARY = "Brook's sckagegroup"
PR = "r0"

inherit packagegroup

PACKAGES = "\
    ${PN}-apps \
    ${PN}-tools \
    "

RDEPENDS_${PN}-apps = "\
    strace \
    "

RDEPENDS_${PN}-tools = "\
    busybox \
    "

RRECOMMENDS_${PN}-tools = "\
    openssl \
    "

加完之後, 可以使用bitbake確認是否可用
jenkins@vista:~/apps_proc/poky/build$ bitbake -s|grep brook
packagegroup-image-brook                              :1.0-r0

接著就可以在images/machine-image-brook.bb中使用這packagegroup
inherit image

IMAGE_INSTALL = "packagegroup-image-brook-tools"

IMAGE_FEATURES = ""

LICENSE = "MIT"

IMAGE_LINGUAS = ""
結果就是會安裝busybox, 當然你也可以安裝"packagegroup-image-brook-apps".

    參考資料:
  • https://www.yoctoproject.org/docs/3.1/dev-manual/dev-manual.html, The Yocto Project Development Tasks Manual



2020年6月26日 星期五

Qualcomm Snapdragon LTE modem


4G X-series Modem Class X5 X7 X12
LTE Category (down / up) 4 6 12/13
Peak LTE speeds down (Mbit/s) 150 300 600
Peak LTE speed up (Mbit/s) 50 50 150
LTE Class LTE (4G) LTE Advanced (4G+)
Modems MDM9628 MDM9635M MDM9645
MDM9625 MDM9235M MDM9640
MDM9320 MDM9630 MDM9340
MDM9225 MDM9330 MDM9245
  MDM9230 MDM9240


5G X-series Modem Class X50 X55
LTE Category (down / up) N/A 22
Peak LTE speeds down (Mbit/s) 2500
Peak LTE speed up (Mbit/s) 316
LTE Class LTE Advanced Pro (4.5G)
5G Modes NSA, TDD FDD, NSA, SA, TDD
Peak 5G speeds down (Mbit/s) 6500 7500
Peak 5G speeds up (Mbit/s) ? 3000


    參考資料:
  • https://en.wikipedia.org/wiki/Qualcomm_Snapdragon_LTE_modem, Qualcomm Snapdragon LTE modem



2020年5月9日 星期六

Linux Kernel(18.2)- SysCall mount


簡單記錄一下Linux 4.19-rc8從mount system call到呼叫file_system_type.mount()的call flow
SYSCALL_DEFINE5(mount)
  |--> ksys_mount()
    |--> do_mount()
      |--> do_new_mount()
        |--> type = get_fs_type()
        |--> vfs_kern_mount(type)
          |--> mount_fs(type)
            |--> type->mount()


SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
        char __user *, type, unsigned long, flags, void __user *, data)
{
    return ksys_mount(dev_name, dir_name, type, flags, data);
}

int ksys_mount(char __user *dev_name, char __user *dir_name, char __user *type,
        unsigned long flags, void __user *data)
{
  int ret;
  char *kernel_type;
  char *kernel_dev;
  void *options;

  kernel_type = copy_mount_string(type);
  ...

  kernel_dev = copy_mount_string(dev_name);
  ...

  options = copy_mount_options(data);
  ...

  ret = do_mount(kernel_dev, dir_name, kernel_type, flags, options);
  ...
  
  return ret;
}

long do_mount(const char *dev_name, const char __user *dir_name,
  const char *type_page, unsigned long flags, void *data_page)
{
  struct path path;
  unsigned int mnt_flags = 0, sb_flags;
  int retval = 0;

  retval = user_path(dir_name, &path);

  if (flags & MS_REMOUNT)
    retval = do_remount(&path, flags, sb_flags, mnt_flags, data_page);
  else if (flags & MS_BIND)
    retval = do_loopback(&path, dev_name, flags & MS_REC);
  else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
    retval = do_change_type(&path, flags);
  else if (flags & MS_MOVE)
    retval = do_move_mount(&path, dev_name);
  else
    retval = do_new_mount(&path, type_page, sb_flags, mnt_flags, dev_name, data_page);
  ...

  return retval;
}

static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
   int mnt_flags, const char *name, void *data)
{
  struct file_system_type *type;
  struct vfsmount *mnt;
  int err;

  type = get_fs_type(fstype);
  ...

  mnt = vfs_kern_mount(type, sb_flags, name, data);
  ...

  put_filesystem(type);
  ...
  err = do_add_mount(real_mount(mnt), path, mnt_flags);
  ...
  return err;
}

struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
  struct mount *mnt;
  mnt = alloc_vfsmnt(name);
  ...

  root = mount_fs(type, flags, name, data);
  ...
  return &mnt->mnt;    
}

struct dentry *
mount_fs(struct file_system_type *type, int flags, const char *name, void *data)
{
  struct dentry *root;
  ...

  root = type->mount(type, flags, name, data)
  ...

  return root;
}



    參考資料:
  • https://lkml.org/lkml/2018/3/16/905, fs: add ksys_mount() helper; remove in-kernel calls to sys_mount()
  • https://www.halolinux.us/kernel-architecture/the-mount-system-call.html, The Mount System Call




2020年5月3日 星期日

Yocto Project Reference Manual - extrausers.bbclass


extrausers.bbclass這個class允許我們在image中加入新的userand group,透過EXTRA_USERS_PARAMS 來設定,如:
新增兩個user,"tester-jim"與"tester-sue" ,其密碼都是tester01
     inherit extrausers
     EXTRA_USERS_PARAMS = "\
         useradd -P tester01 tester-jim; \
         useradd -P tester01 tester-sue; \
         "

也可以用於變更(設定)密碼
     inherit extrausers
     EXTRA_USERS_PARAMS = "\
         usermod -P 1876*18 root; \
         "

更複雜的例子如下
     inherit extrausers
     EXTRA_USERS_PARAMS = "\
         useradd -p '' tester; \
         groupadd developers; \
         userdel nobody; \
         groupdel -g video; \
         groupmod -g 1020 developers; \
         usermod -s /bin/sh tester; \
         "


    參考資料:
  • https://www.yoctoproject.org/docs/1.8/ref-manual/ref-manual.html#ref-classes-extrausers, extrausers.bbclass




2020年4月26日 星期日

how to include a static library in SDK?


這天忽然想build一個static binary, 才發現SDK沒有static library, 於是只好重新build一下SDK with static package.

brook@vista:~/oe-src$ . /opt/oecore-x86_64/environment-setup-cortexa7-neon-vfpv4-oe-linux-gnueabi brook@vista:~/oe-src$ ${CC} -static kobj.c /opt/oecore-x86_64/sysroots/x86_64-oesdk-linux/usr/libexec/arm-oe-linux-gnueabi/gcc/arm-oe-linux-gnueabi/5.3.0/real-ld: error: cannot find -lc /opt/oecore-x86_64/sysroots/cortexa7-neon-vfpv4-oe-linux-gnueabi/usr/lib/crt1.o(.text+0x28): error: undefined reference to '__libc_start_main' /opt/oecore-x86_64/sysroots/cortexa7-neon-vfpv4-oe-linux-gnueabi/usr/lib/crt1.o(.text+0x2c): error: undefined reference to 'abort' /opt/oecore-x86_64/sysroots/cortexa7-neon-vfpv4-oe-linux-gnueabi/usr/lib/crt1.o(.text+0x30): error: undefined reference to '__libc_csu_fini' /opt/oecore-x86_64/sysroots/cortexa7-neon-vfpv4-oe-linux-gnueabi/usr/lib/crt1.o(.text+0x38): error: undefined reference to '__libc_csu_init' /tmp/ccDJTrij.o:kobj.c:function init_hotplug_sock: error: undefined reference to 'bzero' /tmp/ccDJTrij.o:kobj.c:function init_hotplug_sock: error: undefined reference to 'getpid' /tmp/ccDJTrij.o:kobj.c:function init_hotplug_sock: error: undefined reference to 'socket' /tmp/ccDJTrij.o:kobj.c:function init_hotplug_sock: error: undefined reference to 'perror' /tmp/ccDJTrij.o:kobj.c:function init_hotplug_sock: error: undefined reference to 'setsockopt' /tmp/ccDJTrij.o:kobj.c:function init_hotplug_sock: error: undefined reference to 'bind' /tmp/ccDJTrij.o:kobj.c:function init_hotplug_sock: error: undefined reference to 'perror' /tmp/ccDJTrij.o:kobj.c:function init_hotplug_sock: error: undefined reference to 'close' /tmp/ccDJTrij.o:kobj.c:function main: error: undefined reference to 'memset' /tmp/ccDJTrij.o:kobj.c:function main: error: undefined reference to 'recv' /tmp/ccDJTrij.o:kobj.c:function main: error: undefined reference to 'puts' collect2: error: ld returned 1 exit status


這是因為沒有加入staticdev-pkgs
SDKIMAGE_FEATURES += "staticdev-pkgs"
SDKIMAGE_FEATURES相當於IMAGE_FEATURES, 但SDKIMAGE_FEATURES用於建立SDK image用, 如
$ bitbake -c populate_sdk imagename
SDKIMAGE_FEATURES可用的參數與IMAGE_FEATURES相同, 條列如下
  • allow-empty-password: Allows Dropbear and OpenSSH to accept root logins and logins from accounts having an empty password string.
  • dbg-pkgs: Installs debug symbol packages for all packages installed in a given image.
  • debug-tweaks: Makes an image suitable for development (e.g. allows root logins without passwords and enables post-installation logging). See the 'allow-empty-password', 'empty-root-password', and 'post-install-logging' features in this list for additional information.
  • dev-pkgs: Installs development packages (headers and extra library links) for all packages installed in a given image.
  • doc-pkgs: Installs documentation packages for all packages installed in a given image.
  • empty-root-password: Sets the root password to an empty string, which allows logins with a blank password.
  • package-management: Installs package management tools and preserves the package manager database.
  • post-install-logging: Enables logging postinstall script runs to the /var/log/postinstall.log file on first boot of the image on the target system.
  • ptest-pkgs: Installs ptest packages for all ptest-enabled recipes.
  • read-only-rootfs: Creates an image whose root filesystem is read-only. See the "Creating a Read-Only Root Filesystem" section in the Yocto Project Development Manual for more information.
  • splash: Enables showing a splash screen during boot. By default, this screen is provided by psplash, which does allow customization. If you prefer to use an alternative splash screen package, you can do so by setting the SPLASH variable to a different package name (or names) within the image recipe or at the distro configuration level.
  • staticdev-pkgs: Installs static development packages, which are static libraries (i.e. *.a files), for all packages installed in a given image.
Some image features are available only when you inherit the core-image class. The current list of these valid features is as follows:
  • eclipse-debug: Provides Eclipse remote debugging support.
  • hwcodecs: Installs hardware acceleration codecs.
  • nfs-server: Installs an NFS server.
  • qt4-pkgs: Supports Qt4/X11 and demo applications.
  • ssh-server-dropbear: Installs the Dropbear minimal SSH server.
  • ssh-server-openssh: Installs the OpenSSH SSH server, which is more full-featured than Dropbear. Note that if both the OpenSSH SSH server and the Dropbear minimal SSH server are present in IMAGE_FEATURES, then OpenSSH will take precedence and Dropbear will not be installed.
  • tools-debug: Installs debugging tools such as strace and gdb. For information on GDB, see the "Debugging With the GNU Project Debugger (GDB) Remotely" section in the Yocto Project Development Manual. For information on tracing and profiling, see the Yocto Project Profiling and Tracing Manual.
  • tools-profile: Installs profiling tools such as oprofile, exmap, and LTTng. For general information on user-space tools, see the "User-Space Tools" section in the Yocto Project Application Developer's Guide.
  • tools-sdk: Installs a full SDK that runs on the device.
  • tools-testapps: Installs device testing tools (e.g. touchscreen debugging).
  • x11: Installs the X server.
  • x11-base: Installs the X server with a minimal environment.
  • x11-sato: Installs the OpenedHand Sato environment.
    參考資料:
  • https://www.yoctoproject.org/pipermail/yocto/2017-March/035377.html




2020年4月19日 星期日

ANSI C - identifiers naming rule


通常static變數/函數, 我都會以_var, _func為命名方式, 也符合C99 7.1.3標準,
7.1.3 Reserved identifiers

Each header declares or defines all identifiers listed in its associated subclause, and optionally declares or defines identifiers listed in its associated future library directions subclause and identifiers which are always reserved either for any use or for use as file scope identifiers.
-- All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use.
-- All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces.

2020年2月29日 星期六

Linux Kernel(17.2)- Common Device Tree API


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

這章節介紹幾個API,用於存取這些常見的資料型態,會以下的DTS的內容進行parse
/ {
  node1 {
    compatible = "brook,dts-test";
    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 {
    compatible = "brook,dts-test";
    an-empty-property;
    a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
    child-node@1 {
    };
    child-node@2 {
    };
  };
};


  • of_match_device(): Sanity check for device that device is matching with the node
  • of_property_read_string(): To read string property
  • of_property_count_strings(): Find and return the number of strings from a multiple strings property.
  • of_property_read_string_index(): Read a string with index from string-list property.
  • of_find_property(): Find and return the property pointer by giving named
  • of_property_for_each_u32(): A macro to iterate the property to get all values
  • of_property_read_bool(): Returns true if the property exist false otherwise
  • of_find_node_by_name(): Find a node by its "name" property
  • for_each_child_of_node(): Traverse all child device node for current device node

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

static struct of_device_id brook_dt_id[] = {
    {
     .compatible = "brook,dts-test",
    },
    {}
};

MODULE_DEVICE_TABLE(of, brook_dt_id);

static int brook_dts_probe(struct platform_device *pdev)
{
    const struct of_device_id *of_id;
    struct device_node *node, *child;
    struct property *pp;
    int i, ret, of_cnt, len;
    const char *str;
    const __be32 *cur;
    u32 val;

    // sanity check of_match_device() allows to get the matching entry
    of_id = of_match_device(brook_dt_id, &pdev->dev);
    if (!of_id) {
        pr_err("%s: of_id is NULL\n", __func__);
        return -1;
    }

    node = pdev->dev.of_node;
    // To read string property
    ret = of_property_read_string(node, "a-string-property", &str);
    if (!ret) {
        printk("a-string-property: %s\n", str);
    } else {
        printk("no a-string-property\n");
    }

    // Find and return the number of strings from a multiple strings property.
    of_cnt = of_property_count_strings(node, "a-string-list-property");
    if (of_cnt) {
        for (i = 0; i < of_cnt; i++) {
            ret = of_property_read_string_index(node, "a-string-list-property", i, &str);
            if (!ret) {
                printk("a-string-list-property[%d]: %s\n", i, str);
            }
        }
    }

    // to find aproperty named if arg2
    pp = of_find_property(node, "a-byte-data-property", &len);
    if (pp) {
        u8 *u8p = pp->value;
        printk("a-byte-data-property: len:%d, pp->len:%d", len, pp->length);
        for (i = 0; i < len; i++) {
            printk("a-byte-data-property:[%d] = %02x\n", i, u8p[i]);
        }
    }

    // to find aproperty named if arg2
    of_property_for_each_u32(node, "a-cell-property", pp, cur, val) {
        printk("a-cell-property: %04x/%04x\n", *cur, val);
    }

    // Returns true if the property exist false otherwise
    if (of_property_read_bool(node, "an-empty-property")) {
        printk("has an-empty-property\n");
    } else {
        printk("no an-empty-property\n");
    }

    // Traverse all child device node for current device node
    for_each_child_of_node(node, child) {
        printk("child name: %s\n", child->name);
    }

    // Find a node by its "name" property
    child = of_find_node_by_name(node, "child-node1");
    if (child) {
        printk("%s has a child %s\n", node->name, child->name);
    }

    return 0;
}

static int brook_dts_remove(struct platform_device *pdev)
{
    return 0;
}

static struct platform_driver brook_dts_platform_driver = {
    .probe = brook_dts_probe,
    .remove = brook_dts_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "brook-dts",
        .of_match_table = brook_dt_id,
    },
};


static int __init brook_init_module(void)
{
    return platform_driver_register(&brook_dts_platform_driver);
}

static void __exit brook_exit_module(void)
{
    platform_driver_unregister(&brook_dts_platform_driver);
}

module_init(brook_init_module);
module_exit(brook_exit_module);
MODULE_LICENSE("GPL");

執行結果如下:
/ # insmod dts.ko
a-string-property: A string
a-string-list-property[0]: first string
a-string-list-property[1]: second string
a-byte-data-property: len:4, pp->len:4
a-byte-data-property:[0] = 01
a-byte-data-property:[1] = 23
a-byte-data-property:[2] = 34
a-byte-data-property:[3] = 56
no an-empty-property
child name: child-node1
child name: child-node2
node1 has a child child-node1
no a-string-property
a-cell-property: 1000000/0001
a-cell-property: 2000000/0002
a-cell-property: 3000000/0003
a-cell-property: 4000000/0004
has an-empty-property
child name: child-node
child name: child-node


以下摘錄http://www.myexception.cn/linux-unix/1910031.html, linux下devicetree中常用的of函數
linux下devicetree中常用的of函数
从device_node中获取信息:

int of_property_read_u8_array(const struct device_node *np, const char *propname,u8 *out_values, size_t sz);

int of_property_read_u16_array(const struct device_node *np, const char *propname,u16 *out_values, size_t sz);

int of_property_read_u32_array(const struct device_node *np, const char *propname,u32 *out_values, size_t sz);

从设备结点np中读取属性名为propname,类型为8、16、32、位整型数组的属性值,并放入out_values,sz指明了要读取的个数。


static inline int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value) 

static inline int of_property_read_u16(const struct device_node *np,const char *propname,u8 *out_value) 

static inline int of_property_read_u32(const struct device_node *np,const char *propname,u8 *out_value) 

从设备结点np中读取属性名为propname,类型为8、16、32位的属性值,并放入out_values。实际上这里调用的就是sz为1的XXX_array函数。

 

int of_property_read_u32_index(const struct device_node *np,const char*propname,u32 index, u32 *out_value)

从设备结点np中读取属性名为propname的属性值中第index个u32数值给out_value

 

int of_property_read_u64(conststruct device_node *np, const char *propname,u64 *out_value)

从设备结点np中读取属性名为propname,类型为64位的属性值,并放入out_values

 

int of_property_read_string(struct device_node *np, const char *propname,const char**out_string)

从设备结点np中读取属性名为propname的字符串型属性值

 

int of_property_read_string_index(struct device_node *np, const char *propname,intindex, const char **output)

从设备结点np中读取属性名为propname的字符串型属性值数组中的第index个字符串

 

int of_property_count_strings(struct device_node *np, const char *propname)

从设备结点np中读取属性名为propname的字符串型属性值的个数

 

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

从设备节点dev中读取第index个irq号

 

int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)

从设备节点dev中读取第index个irq号,并填充一个irq资源结构体

 

int of_irq_count(struct device_node *dev)

获取设备节点dev的irq个数


static inline bool of_property_read_bool(const struct device_node *np,const char *propname);

如果设备结点np含有propname属性,则返回true,否则返回false。一般用于检查空属性是否存在。

 

struct property* of_find_property(const struct device_node *np,const char *name,int *lenp)

根据name参数,在指定的设备结点np中查找匹配的property,并返回这个property

 

const void * of_get_property(const struct device_node *np, const char *name,int *lenp)

根据name参数,在指定的设备结点np中查找匹配的property,并返回这个property的属性值


struct device_node* of_get_parent(const struct device_node *node)

获得node节点的父节点的device node


int of_device_is_compatible(const struct device_node *device,const char *compat);

判断设备结点device的compatible属性是否包含compat指定的字符串


从of_allnodes中查找信息:

struct device_node* of_find_node_by_path(const char *path)
根据路径参数,在全局链表of_allnodes中,查找匹配的device_node


struct device_node* of_find_node_by_name(struct device_node *from,const char *name)
则根据name在全局链表of_allnodes中查找匹配的device_node,若from=NULL表示从头开始查找


struct device_node* of_find_node_by_type(struct device_node *from,const char *type)

根据设备类型在全局链表of_allnodes中查找匹配的device_node


struct device_node * of_find_compatible_node(struct device_node *from, const char*type, const char,*compatible);

根据compatible的属性值在全局链表of_allnodes中查找匹配的device_node,大多数情况下,from、type为NULL。

 

struct device_node* of_find_node_with_property(struct device_node *from,const char *prop_name)

根据节点属性的name在全局链表of_allnodes中查找匹配的device_node

 

struct device_node* of_find_node_by_phandle(phandle handle)

根据phandle在全局链表of_allnodes中查找匹配的device_node

 

杂:

void __iomem* of_iomap(struct device_node *node, int index);

通过设备结点直接进行设备内存区间的 ioremap(),index是内存段的索引。若设备结点的reg属性有多段,可通过index标示要ioremap的是哪一段,只有1段的情况,index为0

 

unsigned long __init of_get_flat_dt_root(void)

用来查找在dtb中的根节点,好像返回的都是0


int of_alias_get_id(struct device_node *np, const char *stem)

获取节点np对应的aliasid号

 

struct device_node* of_node_get(struct device_node *node)

void of_node_put(struct device_node *node)

device node计数增加/减少


const struct of_device_id* of_match_node(const struct of_device_id *matches,const struct device_node*node)

将matches数组中of_device_id结构的name和type与device node的compatible和type匹配,返回匹配度最高的of_device_id结构


platform_device和resource相关:

int of_address_to_resource(struct device_node *dev, int index,struct resource *r)

根据设备节点dev的reg属性值,填充资源结构体r。Index参数指明了使用reg属性中第几个属性值,一般设置为0,表示第一个。


struct platform_device* of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent)

根据device node,bus_id以及父节点创建该设备的platform_device结构,同时会初始化它的resource成员。

 

int of_platform_bus_probe(struct device_node *root,const struct of_device_id *matches,struct device *parent)

遍历of_allnodes中的节点挂接到of_platform_bus_type总线上,由于此时of_platform_bus_type总线上还没有驱动,所以此时不进行匹配

 

int of_platform_populate(struct device_node *root,const struct of_device_id *matches,const struct of_dev_auxdata *lookup,struct device *parent)

遍历of_allnodes中的所有节点,生成并初始化所以节点的platform_device结构



struct platform_device* of_find_device_by_node(struct device_node *np)

根据device_node查找返回该设备对应的platform_device结构

    參考資料:
  • http://www.myexception.cn/linux-unix/1910031.html, linux下devicetree中常用的of函數
  • https://saurabhsengarblog.wordpress.com/2015/11/28/device-tree-tutorial-arm/, Device Tree Tutorial (ARM)
  • https://www.cnblogs.com/xiaojiang1025/p/6368260.html, Linux内核 设备树操作常用API





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(一)淺析

熱門文章