顯示具有 Linux - kernel 標籤的文章。 顯示所有文章
顯示具有 Linux - kernel 標籤的文章。 顯示所有文章

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



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年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



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)设备树详解之一(背景基础知识篇)




Linux Kernel(17)- Device Tree


Device tree是一個用來描述硬體的資料結構,包含了CPU、Memory、bus與周邊,DT改變了原本kernel的hard-code(table),改由bootloader傳入DTB(Device Tree Blob)給kernel。這個由SPARC-based開始的Open Firmware project於是慢慢地推廣到Arm, x86, MicroBlaze, PowerPC等平台。
這個有趣的故事可以讀一下Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇)

摘錄故事部分如下:
在Linux 2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx,
比如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的
platform_data,这些板级细节代码对内核来讲只不过是垃圾代码。而采用Device Tree后,许多硬件的细节
可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。
每次正式的linux kernel release之后都会有两周的merge window,在这个窗口期间,kernel各个部分的
维护者都会提交各自的patch,将自己测试稳定的代码请求并入kernel main line。每到这个时候,Linus
就会比较繁忙,他需要从各个内核维护者的分支上取得最新代码并merge到自己的kernel source tree中。
Tony Lindgren,内核OMAP development tree的维护者,发送了一个邮件给Linus,请求提交OMAP平台
代码修改,并给出了一些细节描述:
       1)简单介绍本次改动
       2)关于如何解决merge conficts。有些git mergetool就可以处理,不能处理的,
         给出了详细介绍和解决方案。
一切都很平常,也给出了足够的信息,然而,正是这个pull request引发了一场针对ARM linux的内核代码
的争论。我相信Linus一定是对ARM相关的代码早就不爽了,ARM的merge工作量较大倒在其次,主要是他认为
ARM很多的代码都是垃圾,代码里面有若干愚蠢的table,而多个人在维护这个table,从而导致了冲突。
因此,在处理完OMAP的pull request之后(Linus并非针对OMAP平台,只是Tony Lindgren撞在枪口上了)
,他发出了怒吼:
     Gaah.Guys, this whole ARM thing is a f*cking pain in the ass.
 
之后经过一些讨论,对ARM平台的相关code做出如下相关规范调整,这个也正是引入DTS的原因。
1、ARM的核心代码仍然保存在arch/arm目录下
2、ARM SoC core architecture code保存在arch/arm目录下
3、ARM SOC的周边外设模块的驱动保存在drivers目录下
4、ARM SOC的特定代码在arch/arm/mach-xxx目录下
5、ARM SOC board specific的代码被移除,由DeviceTree机制来负责传递硬件拓扑和硬件资源信息。
本质上,Device Tree改变了原来用hardcode方式将HW 配置信息嵌入到内核代码的方法,改用bootloader
传递一个DB的形式。
 ———————————————— 
版权声明:本文为CSDN博主「RadianceBlau」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/RadianceBlau/article/details/70800076

bootloader傳入DTB(Device Tree Blob),而DTB是由Device Tree Source透過DTC編成的binary data,關係概略如下:

DTC相關用法可以參考dtc - Device Tree Compiler
DTS語法會在後面章節介紹,基本上,DTS只描述那些無法動態偵測的設備

    參考資料
  • https://blog.csdn.net/RadianceBlau/article/details/70800076, Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇)
  • https://en.wikipedia.org/wiki/Device_tree, Device tree
  • dtc - Device Tree Compiler




2018年6月17日 星期日

Linux Kernel(15.3)- The Linux usage model for device tree data


這篇基本上就是"Documentation/devicetree/usage-model.txt", The Linux usage model for device tree data的筆記

DT(Device Tree,或稱Open Firmware Device Tree)是一個資料結構(data structure)用於讓OS讀取硬體周邊訊息動態執行,因此OS就不用hard code硬體驅動。

所謂的"bindings"就是一組通用的DTS設定,用來描述其硬體,包含了data busses, interrupt lines, GPIO connections, and peripheral devices等等。

盡可能使用現有的binding來描述硬體,以最大限度地利用現有的代碼,但由於property和node名稱只是字串,因此通過定義新property和node可以輕鬆擴展現有binding。 不要沒做功課就自己建立新的binding,i2c busses就因為沒有先確認是否已經有人定義了相關的binding,就建立了新的binding,導致現在有兩套不相容的binding發生。

DT所做的只是提供一種language將硬體設定從device driver分離開來,如此可以透過傳入不同的DT給OS,以適應不同的硬體設定裝置. 進而減少一些重複的code。

DT在Linux底下有三個主要目的
1) platform identification,
2) runtime configuration, and
3) device population.


platform identification

首先kernel會先使用DT來辨識特定的機器,並且執行相關的初始化,比如ARM會在setup_arch()呼叫setup_machine_fdt()尋找適合的DT(比對DT root底下的"compatible" property。
    compatible = "ti,omap3-beagleboard-xm", "ti,omap3450", "ti,omap3";
上面的例子定義了"ti,omap3-beagleboard-xm",也宣稱相容"OMAP 3450 SOC"與"OMAP3"系列的SoC,你會注意到這樣的宣稱會從最具體的board到SoC的家族,關於compatible的值必須進行記錄說明其含義。

runtime configuration

一般來說,DT是firmware傳遞資料給kernel的唯一方法,PowerPC呼叫of_scan_flat_dt(early_init_dt_scan_root, NULL)執行early init,ARM則是呼叫mdesc = setup_machine_fdt(__atags_pointer)

device population

在early configuration之後,kernel會用 unflatten_device_tree()將DT轉成device node tree,讓之後的init_early(), init_irq() and init_machine()等等使用,init_early()用於任何需要在啟動過程中儘早執行的設置,init_irq()用於設置中斷處理,而init_machine()負責建立Linux platform device,這裡主要呼叫of_platform_populate()建構platform device。之後的driver也是透過of_platform_populate()建構platform device。
i2c_add_driver( ) @ i2c.h  
  |-> i2c_register_driver() @i2c-core.c /* 將device_driver中的bus_type設成i2c_bus_type */  
    |--> driver_register() @driver.c  
      |--> bus_add_driver() @bus.c /* 建立sysfs file node 與 attr */  
        |--> driver_attach() @dd.c  
          |--> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach) @bus.c  
            |--> __driver_attach @dd.c  
              |--> driver_match_device(drv, dev) @base.h  
                |--> i2c_device_match() @i2c-core.c  
                  /***********************************************  
                   如果是device tree, 會透過 of_driver_match_device()去做匹配的動作
                   如果不是device tree就改用 i2c_match_id()去完成匹配的動作
                  ***************************************************/ 
                  /* Attempt an OF style match */
                  if (of_driver_match_device(dev, drv))
                    return 1;

                  /* Then ACPI style match */
                  if (acpi_driver_match_device(dev, drv))
                    return 1;
                  
                  driver = to_i2c_driver(drv);
                  /* match on an id table if there is one */
                  if (driver->id_table)
                    return i2c_match_id(driver->id_table, client) != NULL;
              |--> driver_probe_device() @dd.c  
                /*如果匹配成功, 接下來就要準備 call driver 中的 probe function*/
                |--> really_probe() @dd.c  
                  |--> i2c_device_probe() @i2c-core.c  
                    |--> rt5627_i2c_probe() @rt5627.c  
from http://janbarry0914.blogspot.com/2014/08/device-tree-i2c-device-driver-match.html



2018年1月28日 星期日

dtc - Device Tree Compiler


從dtc manpage可以簡略瞭解一下DTC(Device Tree Compiler)功能, 就是將device-tree的format轉成另一種format, 其input格式有三種,
  1. dts - device tree source text
  2. dtb - device tree blob
  3. fs - /proc/device-tree style directory

而output格式也有三種,
  1. dts - device tree source text
  2. dtb - device tree blob
  3. asm - assembler source

而dtc支援的phandle可支援三種
  1. legacy - "linux,phandle" properties only
  2. epapr - "phandle" properties only
  3. both - Both "linux,phandle" and "phandle" properties

根據Device Tree Mysteries,
what is the reason for having a phandle? 
It is really just a hack to get around the fact that device tree does 
not have a pointer data type. 
It is a way to reference 
"that node over there that is related to this node for some reason". 
簡單來說, phandler就是pointer data type, 用個例子說明
pic@10000000 {
    phandle = < 1 >;
};

A phandle value of 1 is defined. 
Another device node could reference the pic node with a
phandle value of 1:

uart@20000000 {
    interrupt-parent = > 1 >;
};
"phandle = < 1 >"的"1"是一個隨意的unit32數值, 只要不衝突即可, 但是這並不是很好記, 所以DTC貼心的可以用lable來建立phandle,如下範例
PIC_3: pic@10000000 {
    interrupt-controller;
};

uart@20000000 {
    interrupt-parent = < &PIC_3 >;
};
這裡的"&"是告訴DTC後面接一個字串, 是個phandle參考到某個lable, 然後DTC就會幫user建立unit32的數值, 建立出phandle. 除此之外也可以使用full path來取代lable, 如下範例
/{
    soc {
        PIC_3: pic@10000000 {
            interrupt-controller;
        };
    };

    uart@20000000 {
        interrupt-parent = < &PIC_3 >;
    };

    uart@30000000 {
        interrupt-parent = < &{/soc/pic@10000000} >;
    };
};

下面再用一個例子同時說明DTC與phandle
brook@vista:~/dts$ cat test_phandle.dts
/dts-v1/;
/{
    soc {
        PIC_3: pic@10000000 {
           interrupt-controller;
        };
    };

    uart@20000000 {
        interrupt-parent = < &PIC_3 >;
    };

    uart@30000000 {
        interrupt-parent = < &{/soc/pic@10000000} >;
    };
};
brook@vista:~/dts$ dtc -O dtb -I dts test_phandle.dts > test_phandle.dtb
brook@vista:~/dts$ dtc -O dts -I dtb test_phandle.dtb
/dts-v1/;

/ {

        soc {

                pic@10000000 {
                        interrupt-controller;
                        linux,phandle = <0x1>;
                        phandle = <0x1>;
                };
        };

        uart@20000000 {
                interrupt-parent = <0x1>;
        };

        uart@30000000 {
                interrupt-parent = <0x1>;
        };
};

這篇文章主要簡單講解利用dtc來轉換dtb與dts, 順帶說明一下phandle是一種pointer data type, 與其基本原理



2016年2月21日 星期日

Linux Kernel(16.1)- Network Device Driver, simple snull.


這一篇藉由LDD(Linux Device Drivers)中的SNULL來了解最基本的Network Device Driver的架構,本章的sample code比原本的SNULL更為簡化,但是Network Topology是相同的,讓interface sn0/sn1可以透過遠方虛擬的remote0/remote1彼此溝通。

最基本的Network Device Driver的寫法就是allocate network device, "struct net_device"並且賦予hook function, "struct net_device_ops",然後將該network device註冊到kernel中,Kernel就可以調用該Network device,最基本的net_device_ops包含
  • ndo_open() and ndo_validate_addr() are called, when the NIC is bring up.
  • ndo_stop() is called, when the NIC is shut down.
  • ndo_start_xmit() is called, when a packet is sent from the NIC.
  • ndo_change_mtu() is called, when the MTU of the NIC is changed.
  • ndo_set_mac_address() is called, when the MAC address of the NIC is changed.
以下是demo code的net_device_ops部分
static const struct net_device_ops nic_netdev_ops = {
    /* Kernel calls ndo_open() and ndo_validate_addr()
     * when you bring up the NIC
     */
    .ndo_open               = nic_open,
    .ndo_validate_addr      = nic_validate_addr,

    /* when you shut down the NIC, kernel call the .ndo_stop() */
    .ndo_stop               = nic_close,

    /* Kernel calls ndo_start_xmit() when it wants to 
     *   transmit a packet. 
     */
    .ndo_start_xmit         = nic_start_xmit,

    /* ndo_change_mtu() is called, when you change MTU */
    .ndo_change_mtu         = nic_change_mtu,

    /* ndo_set_mac_address() is called,
     *   when you change the MAC addr
     */
    .ndo_set_mac_address    = nic_set_mac_addr,
};


我們是模擬ethernet,而ethernet有一些hook function可以用,如下
  • ndo_validate_addr() -> eth_validate_addr().
  • ndo_change_mtu() -> eth_change_mtu().
  • ndo_set_mac_address() -> eth_mac_addr().
在demo code中,ndo_validate_addr()/ndo_change_mtu()/ndo_set_mac_address()我都是將其轉成ethernet的default hook function,我沒直接掛,是因為我想印出訊息來看
static int nic_validate_addr(struct net_device *netdev)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    return eth_validate_addr(netdev);
}

static int nic_change_mtu(struct net_device *netdev, int new_mtu)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    return eth_change_mtu(netdev, new_mtu);
}

static int nic_set_mac_addr(struct net_device *netdev, void *addr)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    return eth_mac_addr(netdev, addr);
}

另外幾個比較重要的function是netif_start_queue()/netif_stop_queue()
  • netif_start_queue()是通知上層,可以將資料送到該網卡,通常放在ndo_open()裡面
  • netif_stop_queue()是通知上層,停止將資料送到該網卡,通常放在ndo_stop()裡面


由於我們沒有真的remote0/remote1可以回應,所以必須設定flag/IFF_NOARP在sn0跟sn1,並且自己要處理L2的header,所以必須在額外掛上"struct header_ops"。

以下為完整的driver,主要code都是印訊息觀察driver的call flow
/* reference ldd3, snull.c */
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>

/* for in_device, in_ifaddr */
#include <linux/inetdevice.h>

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

#define MAX_ETH_FRAME_SIZE   1792
struct nic_priv {
    /* you can use array to queue more packet */
    unsigned char *tx_buf;
    unsigned int  tx_len;
    u32           msg_enable;
};

static struct net_device *nic_dev[2];
/* netif msg type, defined in netdevice.h
    NETIF_MSG_DRV           = 0x0001,
    NETIF_MSG_PROBE         = 0x0002,
    NETIF_MSG_LINK          = 0x0004,
    NETIF_MSG_TIMER         = 0x0008,
    NETIF_MSG_IFDOWN        = 0x0010,
    NETIF_MSG_IFUP          = 0x0020,
    NETIF_MSG_RX_ERR        = 0x0040,
    NETIF_MSG_TX_ERR        = 0x0080,
    NETIF_MSG_TX_QUEUED     = 0x0100,
    NETIF_MSG_INTR          = 0x0200,
    NETIF_MSG_TX_DONE       = 0x0400,
    NETIF_MSG_RX_STATUS     = 0x0800,
    NETIF_MSG_PKTDATA       = 0x1000,
    NETIF_MSG_HW            = 0x2000,
    NETIF_MSG_WOL           = 0x4000,
*/
#define DEF_MSG_ENABLE 0xffff

static void dump(unsigned char *buf)
{
    unsigned char *p, sbuf[2*(sizeof(struct ethhdr) + sizeof(struct iphdr))];
    int i;
    p = sbuf;

    for(i = 0; i < sizeof(struct ethhdr); i++) {
        p += sprintf(p, "%02X ", buf[i]);
    }
    printk("eth %s\n", sbuf);

    p = sbuf;
    for(i = 0; i < sizeof(struct iphdr); i++) {
        p += sprintf(p, "%02X ", buf[sizeof(struct ethhdr) + i]);
    }
    printk("iph %s\n", sbuf);

    p = sbuf;
    for(i = 0; i < 4; i++) {
        p += sprintf(p, "%02X ", buf[sizeof(struct ethhdr) + sizeof(struct iphdr) + i]);
    }
    printk("payload %s\n", sbuf);
}


static void
nic_rx(struct net_device *netdev, int len, unsigned char *buf)
{
    struct sk_buff *skb;
    struct nic_priv *priv = netdev_priv(netdev);

    netif_info(priv, hw, netdev, "%s(#%d), rx:%d\n",
                __func__, __LINE__, len);
    /*
     * The packet has been retrieved from the transmission
     * medium. Build an skb around it, so upper layers can handle it
     */
    skb = dev_alloc_skb(len + 2);
    if (!skb) {
        netif_err(priv, rx_err, netdev,
                  "%s(#%d), rx: low on mem - packet dropped\n",
                  __func__, __LINE__);
        netdev->stats.rx_dropped++;
        return;
    }
    skb_reserve(skb, 2); /* align IP on 16B boundary */
    memcpy(skb_put(skb, len), buf, len);

    /* Write metadata, and then pass to the receive level */
    skb->dev = netdev;
    skb->protocol = eth_type_trans(skb, netdev);
    skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
    netdev->stats.rx_packets++;
    netdev->stats.rx_bytes += len;
    netif_rx(skb);
}

static int nic_open(struct net_device *netdev)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, ifup, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    /* may be using DMA */
    priv->tx_buf = kmalloc(MAX_ETH_FRAME_SIZE, GFP_KERNEL);
    if (priv->tx_buf == NULL) {
        netif_info(priv, ifup, netdev, "%s(#%d), cannot alloc tx buf\n",
                    __func__, __LINE__);
        return -ENOMEM;
    }
    netif_start_queue(netdev);
    return 0;
}

static int nic_close(struct net_device *netdev)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, ifdown, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    netif_stop_queue(netdev);
    return 0;
}

static void nic_hw_xmit(struct net_device *netdev)
{
    struct nic_priv *priv = netdev_priv(netdev);
    struct iphdr *iph;
    u32 *saddr, *daddr;
    struct in_device* in_dev;
    struct in_ifaddr* if_info;

    if (priv->tx_len < sizeof(struct ethhdr) + sizeof(struct iphdr)) {
        netif_info(priv, hw, netdev, "%s(#%d), too short\n",
                   __func__, __LINE__);
        return;
    }
    dump(priv->tx_buf);
    iph = (struct iphdr *)(priv->tx_buf + sizeof(struct ethhdr));
    saddr = &iph->saddr;
    daddr = &iph->daddr;

    netif_info(priv, hw, netdev, "%s(#%d), orig, src:%pI4, dst:%pI4, len:%d\n",
                __func__, __LINE__, saddr, daddr, priv->tx_len);

    in_dev = nic_dev[(netdev == nic_dev[0] ? 1 : 0)]->ip_ptr;
    if (in_dev) {
        if_info = in_dev->ifa_list;
        for (if_info = in_dev->ifa_list; if_info; if_info=if_info->ifa_next) {
#if 0
            printk("label:%s, address=%pI4\n",
               if_info->ifa_label, &if_info->ifa_address);
#endif
            *saddr = *daddr = if_info->ifa_address;
            ((u8 *)saddr)[3]++;
            netif_info(priv, hw, netdev, "%s(#%d), new, src:%pI4, dst:%pI4\n",
                        __func__, __LINE__, saddr, daddr);
            break;
        }
        if (!if_info) {
            /* drop packet */
            netdev->stats.tx_dropped++;
            netif_info(priv, hw, netdev, "%s(#%d), drop packet\n",
                        __func__, __LINE__);
            return;
        }
    }

    iph->check = 0;         /* and rebuild the checksum (ip needs it) */
    iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);

    netdev->stats.tx_packets++;
    netdev->stats.tx_bytes += priv->tx_len;

    nic_rx(nic_dev[(netdev == nic_dev[0] ? 1 : 0)], priv->tx_len, priv->tx_buf);
}

static netdev_tx_t nic_start_xmit(struct sk_buff *skb,
                                  struct net_device *netdev)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), orig, src:%pI4, dst:%pI4\n",
                __func__, __LINE__, &(ip_hdr(skb)->saddr), &(ip_hdr(skb)->daddr));
    priv->tx_len = skb->len;
    if (likely(priv->tx_len < MAX_ETH_FRAME_SIZE)) {
        if (priv->tx_len < ETH_ZLEN) {
            memset(priv->tx_buf, 0, ETH_ZLEN);
            priv->tx_len = ETH_ZLEN;
        }
        skb_copy_and_csum_dev(skb, priv->tx_buf);
        dev_kfree_skb_any(skb);
    } else {
        dev_kfree_skb_any(skb);
        netdev->stats.tx_dropped++;
        return NETDEV_TX_OK;
    }

    nic_hw_xmit(netdev);
    return NETDEV_TX_OK;
}

static int nic_validate_addr(struct net_device *netdev)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    return eth_validate_addr(netdev);
}

static int nic_change_mtu(struct net_device *netdev, int new_mtu)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    return eth_change_mtu(netdev, new_mtu);
}

static int nic_set_mac_addr(struct net_device *netdev, void *addr)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    return eth_mac_addr(netdev, addr);
}

/*
 * This function is called to fill up an eth header, since arp is not
 * available on the interface
 */
int snull_header(struct sk_buff *skb, struct net_device *netdev,
                unsigned short type, const void *daddr, const void *saddr,
                unsigned len)
{
    struct nic_priv *priv = netdev_priv(netdev);
    struct ethhdr *eth = (struct ethhdr *)skb_push(skb, ETH_HLEN);
    struct net_device *dst_netdev;

    netif_info(priv, drv, netdev, "%s(#%d)\n",
                __func__, __LINE__);
    dst_netdev = nic_dev[(netdev == nic_dev[0] ? 1 : 0)];
    eth->h_proto = htons(type);
    memcpy(eth->h_source, saddr ? saddr : netdev->dev_addr, netdev->addr_len);
    memcpy(eth->h_dest, dst_netdev->dev_addr, dst_netdev->addr_len);
    return (netdev->hard_header_len);
}

static const struct header_ops snull_header_ops = {
        .create  = snull_header,
};

static const struct net_device_ops nic_netdev_ops = {
    /* Kernel calls ndo_open() and ndo_validate_addr()
     * when you bring up the NIC
     */
    .ndo_open               = nic_open,
    .ndo_validate_addr      = nic_validate_addr,

    /* when you shut down the NIC, kernel call the .ndo_stop() */
    .ndo_stop               = nic_close,

    /* Kernel calls ndo_start_xmit() when it wants to 
     *   transmit a packet. 
     */
    .ndo_start_xmit         = nic_start_xmit,

    /* ndo_change_mtu() is called, when you change MTU */
    .ndo_change_mtu         = nic_change_mtu,

    /* ndo_set_mac_address() is called,
     *   when you change the MAC addr
     */
    .ndo_set_mac_address    = nic_set_mac_addr,
};

static struct net_device* nic_alloc_netdev(void)
{
    struct net_device *netdev;

    netdev = alloc_etherdev(sizeof(struct nic_priv));
    if (!netdev) {
        pr_err("%s(#%d): alloc dev failed",
               __func__, __LINE__);
        return NULL;
    }
    eth_hw_addr_random(netdev);
    netdev->netdev_ops = &nic_netdev_ops;

    /* keep the default flags, just add NOARP */
    netdev->flags |= IFF_NOARP;

    /* There are no explicit users, so this is 
     *     now equivalent to NETIF_F_HW_CSUM. */
    netdev->features |= NETIF_F_HW_CSUM;

    netdev->header_ops = &snull_header_ops;

    return netdev;
}

static int __init brook_init(void)
{
    int ret;
    struct nic_priv *priv;

    nic_dev[0] = nic_alloc_netdev();
    if (!nic_dev[0]) {
        pr_err("%s(#%d): alloc netdev[0] failed", __func__, __LINE__);
        return -ENOMEM;
    }

    nic_dev[1] = nic_alloc_netdev();
    if (!nic_dev[1]) {
        pr_err("%s(#%d): alloc netdev[1] failed", __func__, __LINE__);
        ret = -ENOMEM;
        goto alloc_2nd_failed;
    }

    ret = register_netdev(nic_dev[0]);
    if (ret) {
        pr_err("%s(#%d): reg net driver failed. ret:%d",
               __func__, __LINE__, ret);
        goto reg1_failed;
    }

    ret = register_netdev(nic_dev[1]);
    if (ret) {
        pr_err("%s(#%d): reg net driver failed. ret:%d",
               __func__, __LINE__, ret);
        goto reg2_failed;
    }

    priv = netdev_priv(nic_dev[0]);
    priv->msg_enable = DEF_MSG_ENABLE;
    priv = netdev_priv(nic_dev[1]);
    priv->msg_enable = DEF_MSG_ENABLE;
    return 0;

reg2_failed:
    unregister_netdev(nic_dev[0]);
reg1_failed:
    free_netdev(nic_dev[1]);
alloc_2nd_failed:
    free_netdev(nic_dev[0]);
    return ret;
}
module_init(brook_init);

static void __exit brook_exit(void)
{
    int i;
    pr_info("%s(#%d): remove module", __func__, __LINE__);
    for (i = 0; i < ARRAY_SIZE(nic_dev); i++) {
        unregister_netdev(nic_dev[i]);
        free_netdev(nic_dev[i]);
    }
}
module_exit(brook_exit);





參考資料:
  1. Linux Device Drivers, Third Edition, Chapter 17: Network Drivers, https://lwn.net/Kernel/LDD3/,
  2. Linux Networking and Network Devices APIs, https://www.kernel.org/doc/htmldocs/networking/index.html




2016年1月3日 星期日

Linux Kernel(15.2)- platform_device_register()之如何调用driver.probe()


Linux Kernel(16.1)- platform_driver_register()之如何调用driver.probe()之後,我們來看如何由platform_device_register调用driver.probe()

int platform_device_register(struct platform_device *pdev)
{
    device_initialize(&pdev->dev);
    arch_setup_pdev_archdata(pdev);
    return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);


int platform_device_add(struct platform_device *pdev)
{
    int i, ret;

    if (!pdev)
        return -EINVAL;

    if (!pdev->dev.parent)
        pdev->dev.parent = &platform_bus;

    pdev->dev.bus = &platform_bus_type;

    switch (pdev->id) {
    default:
        dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);
        break;
    case PLATFORM_DEVID_NONE:
        dev_set_name(&pdev->dev, "%s", pdev->name);
        break;
    case PLATFORM_DEVID_AUTO:
        /*
         * Automatically allocated device ID. We mark it as such so
         * that we remember it must be freed, and we append a suffix
         * to avoid namespace collision with explicit IDs.
         */
        ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
        if (ret < 0)
            goto err_out;
        pdev->id = ret;
        pdev->id_auto = true;
        dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
        break;
    }

    for (i = 0; i < pdev->num_resources; i++) {
        struct resource *p, *r = &pdev->resource[i];

        if (r->name == NULL)
            r->name = dev_name(&pdev->dev);

        p = r->parent;
        if (!p) {
            if (resource_type(r) == IORESOURCE_MEM)
                p = &iomem_resource;
            else if (resource_type(r) == IORESOURCE_IO)
                p = &ioport_resource;
        }

        if (p && insert_resource(p, r)) {
            dev_err(&pdev->dev, "failed to claim resource %d\n", i);
            ret = -EBUSY;
            goto failed;
        }
    }

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

    ret = device_add(&pdev->dev);
    if (ret == 0)
        return ret;

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

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

 err_out:
    return ret;
}


int device_add(struct device *dev) 
{
    struct device *parent = NULL;
    struct kobject *kobj;
    struct class_interface *class_intf;
    int error = -EINVAL;

    dev = get_device(dev);
    if (!dev)
        goto done;

    if (!dev->p) {
        error = device_private_init(dev);
        if (error)
            goto done;
    }

    /*
     * for statically allocated devices, which should all be converted
     * some day, we need to initialize the name. We prevent reading back
     * the name, and force the use of dev_name()
     */
    if (dev->init_name) {
        dev_set_name(dev, "%s", dev->init_name);
        dev->init_name = NULL;
    }

    /* subsystems can specify simple device enumeration */
    if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
        dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);

    if (!dev_name(dev)) {
        error = -EINVAL;
        goto name_error;
    }

    pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

    parent = get_device(dev->parent);
    kobj = get_device_parent(dev, parent);
    if (kobj)
        dev->kobj.parent = kobj;

    /* use parent numa_node */
    if (parent)
        set_dev_node(dev, dev_to_node(parent));

    /* first, register with generic layer. */
    /* we require the name to be set before, and pass NULL */
    error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
    if (error)
        goto Error;

    /* notify platform of device entry */
    if (platform_notify)
        platform_notify(dev);

    error = device_create_file(dev, &dev_attr_uevent);
    if (error)
        goto attrError;

    error = device_add_class_symlinks(dev);
    if (error)
        goto SymlinkError;
    error = device_add_attrs(dev);
    if (error)
        goto AttrsError;
    error = bus_add_device(dev);
    if (error)
        goto BusError;
    error = dpm_sysfs_add(dev);
    if (error)
        goto DPMError;
    device_pm_add(dev);

    if (MAJOR(dev->devt)) {
        error = device_create_file(dev, &dev_attr_dev);
        if (error)
            goto DevAttrError;

        error = device_create_sys_dev_entry(dev);
        if (error)
            goto SysEntryError;

        devtmpfs_create_node(dev);
    }

    /* Notify clients of device addition.  This call must come
     * after dpm_sysfs_add() and before kobject_uevent().
     */
    if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                         BUS_NOTIFY_ADD_DEVICE, dev);

    kobject_uevent(&dev->kobj, KOBJ_ADD);
    bus_probe_device(dev);
    if (parent)
        klist_add_tail(&dev->p->knode_parent,
                   &parent->p->klist_children);

    if (dev->class) {
        mutex_lock(&dev->class->p->mutex);
        /* tie the class to the device */
        klist_add_tail(&dev->knode_class,
                   &dev->class->p->klist_devices);

        /* notify any interfaces that the device is here */
        list_for_each_entry(class_intf,
                    &dev->class->p->interfaces, node)
            if (class_intf->add_dev)
                class_intf->add_dev(dev, class_intf);
        mutex_unlock(&dev->class->p->mutex);
    }
done:
    put_device(dev);
    return error;
 SysEntryError:
    if (MAJOR(dev->devt))
        device_remove_file(dev, &dev_attr_dev);
 DevAttrError:
    device_pm_remove(dev);
    dpm_sysfs_remove(dev);
 DPMError:
    bus_remove_device(dev);
 BusError:
    device_remove_attrs(dev);
 AttrsError:
    device_remove_class_symlinks(dev);
 SymlinkError:
    device_remove_file(dev, &dev_attr_uevent);
 attrError:
    kobject_uevent(&dev->kobj, KOBJ_REMOVE);
    kobject_del(&dev->kobj);
 Error:
    cleanup_device_parent(dev);
    put_device(parent);
name_error:
    kfree(dev->p);
    dev->p = NULL;
    goto done;
}


void bus_probe_device(struct device *dev)
{
    struct bus_type *bus = dev->bus;
    struct subsys_interface *sif;

    if (!bus)
        return;

    if (bus->p->drivers_autoprobe)
        device_initial_probe(dev);

    mutex_lock(&bus->p->mutex);
    list_for_each_entry(sif, &bus->p->interfaces, node)
        if (sif->add_dev)
            sif->add_dev(dev, sif);
    mutex_unlock(&bus->p->mutex);
}


void device_initial_probe(struct device *dev)
{
    __device_attach(dev, true);
}


static int __device_attach(struct device *dev, bool allow_async)
{
    int ret = 0;

    device_lock(dev);
    if (dev->driver) {
        if (klist_node_attached(&dev->p->knode_driver)) {
            ret = 1;
            goto out_unlock;
        }
        ret = device_bind_driver(dev);
        if (ret == 0)
            ret = 1;
        else {
            dev->driver = NULL;
            ret = 0;
        }
    } else {
        struct device_attach_data data = {
            .dev = dev,
            .check_async = allow_async,
            .want_async = false,
        };

        if (dev->parent)
            pm_runtime_get_sync(dev->parent);

        ret = bus_for_each_drv(dev->bus, NULL, &data,
                    __device_attach_driver);
        if (!ret && allow_async && data.have_async) {
            /*
             * If we could not find appropriate driver
             * synchronously and we are allowed to do
             * async probes and there are drivers that
             * want to probe asynchronously, we'll
             * try them.
             */
            dev_dbg(dev, "scheduling asynchronous probe\n");
            get_device(dev);
            async_schedule(__device_attach_async_helper, dev);
        } else {
            pm_request_idle(dev);
        }

        if (dev->parent)
            pm_runtime_put(dev->parent);
    }
out_unlock:
    device_unlock(dev);
    return ret;
}


static int __device_attach_driver(struct device_driver *drv, void *_data)
{
    struct device_attach_data *data = _data;
    struct device *dev = data->dev;
    bool async_allowed;

    /*
     * Check if device has already been claimed. This may
     * happen with driver loading, device discovery/registration,
     * and deferred probe processing happens all at once with
     * multiple threads.
     */
    if (dev->driver)
        return -EBUSY;

    if (!driver_match_device(drv, dev))
        return 0;

    async_allowed = driver_allows_async_probing(drv);

    if (async_allowed)
        data->have_async = true;

    if (data->check_async && async_allowed != data->want_async)
        return 0;

    return driver_probe_device(drv, dev);
}


static inline int driver_match_device(struct device_driver *drv,
                      struct device *dev)
{
    return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}





熱門文章