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



熱門文章