2024年5月5日 星期日

A pattern for state machine III - SM framework


科技始終來自人性。最近剛好看到別人寫的SM有點糟糕,於是想起自己之前寫的,感覺也是不夠直覺,於是改寫了一下。主要概念還是根據SM的定義。
    An abstract state machine is a software component that defines a finite set of states:
  • One state is defined as the initial state. When a machine starts to execute, it automatically enters this state.
  • Each state can define actions that occur when a machine enters or exits that state.
  • Each state can define events that trigger a transition.
  • A transition defines how a machine would react to the event, by exiting one state and entering another state.
所以就從需求先定義API,再來實作內容。首先想到的是需要一個API來初始化這個SM,於是就有sm_alloc()誕生,並回傳sm這個抽象結構,sm_free()是用來釋放該SM的(destroy)。
typedef void * sm;
sm sm_alloc(char *name, void *data);
int sm_free(sm s);

Each state can define actions that occur when a machine enters or exits that state. 這句化,建立了API int sm_state_add(sm s, int state, sm_fp enter, sm_fp exit),用以建立sm中的"狀態",並且綁定enter action與exit action。
typedef int(*sm_fp)(void *data);
int sm_state_add(sm s, int state, sm_fp enter, sm_fp exit);
int sm_state_del(sm s, int state);

Each state can define events that trigger a transitionA transition defines how a machine would react to the event, by exiting one state and entering another state.這句話中,建立了API int sm_event_add(sm s, int state, int event, int new_state, sm_fp action),用於建立狀態中的"事件",指定"新狀態",以及"事件對應的動作"。並且使用API int sm_run(sm s, int new_event)讓sm根據收到的"事件"執行。
int sm_event_add(sm s, int state, int event, int new_state, sm_fp action);
int sm_event_del(sm s, int state, int event);
int sm_run(sm s, int new_event);

根據One state is defined as the initial state. When a machine starts to execute, it automatically enters this state.,我們定義了API int sm_init_state_set(sm s, int state)用以設定"初始狀態"。
int sm_current_state(sm s);

至此所有API都齊全了,以下是sm.h,其中我用marco稍微讓這個API多存一些資訊。
#ifndef _SM_H_
#define _SM_H_

typedef void * sm; /**< State machine handle */
typedef int(*sm_fp)(void *data); /**< State machine function pointer */

sm sm_alloc(char *name, void *data); /**< Allocate a state machine */
int sm_free(sm s); /**< Free a state machine */

#define sm_state_add(s, state, enter, exit) _sm_state_add(s, state, #state, (sm_fp)enter, #enter, (sm_fp)exit, #exit)
int _sm_state_add(sm s, int state, const char *st_name, sm_fp enter, const char * ent_fname, sm_fp exit, const char * exit_fname); /**< Add a state */

int sm_state_del(sm s, int state); /**< Delete a state */
int sm_run(sm s, int new_event); /**< Run the state machine */

#define sm_event_add(s, state, event, new_state, action) _sm_event_add(s, state, event, #event, new_state, (sm_fp)action, #action)
int _sm_event_add(sm s, int state, int event, const char *ev_name, int new_state, sm_fp action, const char *action_fname); /**< Add an event */
int sm_event_del(sm s, int state, int event); /**< Delete an event */
int sm_init_state_set(sm s, int state); /**< Set the initial state */
int sm_current_state(sm s); /**< Get the current state */
int sm_dump_state(sm s); /**< Dump the state machine */
#endif
接下來要解釋一下每個API的時作內容,首先是sm sm_alloc(char *name, void *data),我的想法是不限制有多少"狀態",所以要用link list去串SM中的每一個狀態,每個狀態都去串始於自己的"事件",為了在刪除"狀態"時,也能刪除指向該"狀態"的"事件",於於是我在每個"狀態"中多存了"被指到的事件(pointed_event_ll)"。
#include "sm.h"
#include "list.h"

struct sm_state {
    struct list_head state_ll;
    int state;
    const char *st_name;
    sm_fp enter;
    const char *ent_fname;
    sm_fp exit;
    const char *exit_fname;
    struct list_head event_ll;
    struct list_head pointed_event_ll;
};

struct _sm {
    void *v;
    char *name;
    struct list_head state_ll;
    struct sm_state *cur_sm_st;
    pthread_mutex_t mutex;
};

sm sm_alloc(char *name, void *data)
{
    struct _sm *sm;
    sm = (struct _sm *) malloc(sizeof(struct _sm));
    if (!sm) {
        sm_pr_err("malloc failed\n");
        return NULL;
    }
    sm->v = data;
    sm->name = strdup(name);
    sm->cur_sm_st = NULL;
    if (!sm->name) {
        sm_pr_err("malloc name failed\n");
        free(sm);
        return NULL;
    }
    INIT_LIST_HEAD(&apm;sm->state_ll);
    pthread_mutex_init(&sm->mutex, NULL);
    return sm;
}

int sm_free(sm s)
{
    struct _sm *sm = (struct _sm *) s;
    struct sm_state *sm_st, *tmp_sm_st;
    struct sm_event *sm_ev, *tmp_sm_ev;
    if (!sm) {
        return -1;
    }
    list_for_each_entry_safe(sm_st, tmp_sm_st, &sm->state_ll, state_ll) {
        list_for_each_entry_safe(sm_ev, tmp_sm_ev, &st->event_ll, event_ll) {
            list_del(&sm_ev->event_ll);
            free(sm_ev);
        }
        list_del(&sm_st->state_ll);
        free(sm_st);
    }
    pthread_mutex_destroy(&sm->mutex);
    free(sm->name);
    free(sm);
    return 0;
}

接著要說一下int sm_state_add(sm s, int state, sm_fp enter, sm_fp exit),其實只要判斷不存在要建立的state,剩下就是把資訊存到struct sm_state *而已,而sm_state_del()就是把對應的event都刪除後,釋放對應的resource。
static struct sm_state *sm_get_sm_state(sm s, int state)
{
    struct _sm *sm = (struct _sm *) s;
    struct sm_state *sm_st;
    list_for_each_entry(sm_st, &sm->state_ll, state_ll) {
        if (sm_st->state == state) {
            return st;
        }
    }
    return NULL;
}

int _sm_state_add(sm s, int state, const char *st_name, sm_fp enter, const char * ent_fname, sm_fp exit, const char *exit_fname)
{
    struct _sm *sm = (struct _sm *) s;
    struct sm_state *sm_st;
    if (!sm) {
        sm_pr_err("invalid argument\n");
        return -1;
    }
    // if state is already exist, return -1
    sm_st = sm_get_sm_state(s, state);
    if (sm_st) {
        sm_pr_err("state exist\n");
        return -1;
    }

    sm_st = (struct sm_state *) malloc(sizeof(struct sm_state));
    if (!sm_st) {
        sm_pr_err("malloc failed\n");
        return -1;
    }
    sm_st->state = state;
    sm_st->st_name = st_name;
    sm_st->enter = enter;
    sm_st->ent_fname = ent_fname;
    sm_st->exit = exit;
    sm_st->exit_fname = exit_fname;
    INIT_LIST_HEAD(&sm_st->event_ll);
    INIT_LIST_HEAD(&sm_st->pointed_event_ll);
    list_add_tail(&sm_st->state_ll, &sm->state_ll);
    return 0;
}

int sm_state_del(sm s, int state)
{
    struct _sm *sm = (struct _sm *) s;
    struct sm_state *sm_st;
    struct sm_event *sm_ev, *tmp_sm_ev;
    if (!sm) {
        sm_pr_err("invalid argument\n");
        return -1;
    }

    sm_st = sm_get_sm_state(s, state);
    if (!sm_st) {
        sm_pr_err("state is not exist\n");
        return -1;
    }

    list_for_each_entry_safe(sm_ev, tmp_sm_ev, &sm_st->event_ll, event_ll) {
        list_del(&sm_ev->event_ll);
        free(sm_ev);
    }
    list_for_each_entry_safe(sm_ev, tmp_sm_ev, &sm_st->pointed_event_ll, pointed_event_ll) {
        sm_st = sm_ev->sm_state;
        list_del(&sm_ev->pointed_event_ll);
    }
    list_del(&sm_st->state_ll);
    free(sm_st);
    return 0;
}

int sm_event_add(sm s, int state, int event, int new_state, sm_fp action);要先判斷"狀態"與"新狀態"存在,且"事件"不存在,接著把該"事件"串到該"狀態"去。
static struct sm_event *sm_get_sm_event(struct sm_state *st, int event)
{
    struct sm_event *sm_ev;
    list_for_each_entry(sm_ev, &st->event_ll, event_ll) {
        if (sm_ev->event == event) {
            return sm_ev;
        }
    }
    return NULL;
}

int _sm_event_add(sm s, int state, int event, const char *ev_name, int new_state, sm_fp action, const char *action_fname)
{
    struct _sm *sm = (struct _sm *) s;
    struct sm_state *sm_st, *new_sm_st;
    struct sm_event *sm_ev;
    if (!sm) {
        sm_pr_err("invalid argument\n");
        return -1;
    }
    sm_st = sm_get_sm_state(s, state);
    if (!sm_st) {
        sm_pr_err("state is not exist\n");
        return -1;
    }

    sm_ev = sm_get_sm_event(sm_st, event);
    if (sm_ev) {
        sm_pr_err("event is already exist\n");
        return -1;
    }

    new_sm_st = sm_get_sm_state(s, new_state);
    if (!new_sm_st) {
        sm_pr_err("new state is not exist\n");
        return -1;
    }

    sm_ev = (struct sm_event *) malloc(sizeof(struct sm_event));
    if (!sm_ev) {
        sm_pr_err("malloc failed\n");
        return -1;
    }
    sm_ev = (struct sm_event *) malloc(sizeof(struct sm_event));
    if (!sm_ev) {
        sm_pr_err("malloc failed\n");
        return -1;
    }
    sm_ev->sm_state = sm_st;
    sm_ev->event = event;
    sm_ev->ev_name = ev_name;
    sm_ev->new_sm_state = new_sm_st;
    sm_ev->action = action;
    sm_ev->action_fname = action_fname;
    list_add_tail(&sm_ev->event_ll, &sm_st->event_ll);
    list_add_tail(&sm_ev->pointed_event_ll, &new_sm_st->pointed_event_ll);
    return 0;
}

int sm_event_del(sm s, int state, int event)
{
    struct _sm *sm = (struct _sm *) s;
    struct sm_state *sm_st;
    struct sm_event *sm_ev;
    if (!sm) {
        sm_pr_err("invalid argument\n");
        return -1;
    }
    sm_st = sm_get_sm_state(s, state);
    if (!sm_st) {
        sm_pr_err("state is not exist\n");
        return -1;
    }
    sm_ev = sm_get_sm_event(sm_st, event);
    if (!sm_ev) {
        sm_pr_err("event is not exist\n");
        return -1;
    }
    list_del(&sm_ev->event_ll);
    free(sm_ev);
    return 0;
}

int sm_init_state_set(sm s, int state),其實就是找到,該"狀態",然後把SM的cur_sm_st指向它
int sm_init_state_set(sm s, int state)
{
    struct _sm *sm = (struct _sm *) s;
    struct sm_state *sm_st;
    if (!sm) {
        sm_pr_err("invalid argument\n");
        return -1;
    }
    sm_st = sm_get_sm_state(s, state);
    if (!sm_st) {
        sm_pr_err("state is not exist\n");
        return -1;
    }
    sm->cur_sm_st = sm_st;
    return 0;
}

最後是 int sm_run(sm s, int event),從cur_sm_st去找對應的"事件",如果找到,就執行離開該"狀態"的"動作",接著觸發該"事件"的"動作",最後設定"新狀態"為cur_sm_st,並執行新狀態的進入"動作"
int sm_run(sm s, int event)
{
    struct _sm *sm = (struct _sm *) s;
    struct sm_state *sm_st;
    struct sm_event *sm_ev;
    if (!sm) {
        sm_pr_err("invalid argument\n");
        return -1;
    }
    pthread_mutex_lock(&sm->mutex);
    if (!sm->cur_sm_st) {
        sm_pr_err("invalid stats\n");
        pthread_mutex_unlock(&sm->mutex);
        return -1;
    }

    sm_st = sm->cur_sm_st;
    sm_ev = sm_get_sm_event(sm_st, event);
    if (!sm_ev) {
        sm_pr_err("event is not exist\n");
        pthread_mutex_unlock(&sm->mutex);
        return -1;
    }
    if (sm->cur_sm_st->exit) {
        sm->cur_sm_st->exit(sm->v);
    }
    if (sm_ev->action) {
        sm_ev->action(sm->v);
    }
    sm->cur_sm_st = sm_ev->new_sm_state;
    if (sm->cur_sm_st->enter) {
        sm->cur_sm_st->enter(sm->v);
    }
    
    pthread_mutex_unlock(&sm->mutex);
    return 0;
}

剩下的僅是一些協助的API
int sm_current_state(sm s)
{
    struct _sm *sm = (struct _sm *) s;
    if (!sm) {
        sm_pr_err("invalid argument\n");
        return -1;
    }
    return sm->cur_sm_st->state;
}

int sm_dump_state(sm s)
{
    struct _sm *sm = (struct _sm *) s;
    struct sm_state *sm_st, *ori_sm_st;
    struct sm_event *sm_ev;
    if (!sm) {
        sm_pr_err("invalid argument\n");
        return -1;
    }
    list_for_each_entry(sm_st, &sm->state_ll, state_ll) {
        printf("state: %d/%p/%s\n", sm_st->state, sm_st, sm_st->st_name);
        printf("\tenter_fp: %p, enter_fname: %s\n", sm_st->enter, sm_st->enter?sm_st->ent_fname:"");
        printf("\texit_fp: %p, exit_fname: %s\n", sm_st->exit, sm_st->exit?sm_st->exit_fname:"");
        list_for_each_entry(sm_ev, &sm_st->event_ll, event_ll) {
            printf("\tevent: %d/%p/%s, new_state: %d/%p/%s, ev_fp:%p/%s\n", sm_ev->event, sm_ev, sm_ev->ev_name,
                            sm_ev->new_sm_state->state, sm_ev->new_sm_state, sm_ev->new_sm_state->st_name,
                            sm_ev->action, sm_ev->action?sm_ev->action_fname:"");
        }
        // pointed event is the event that point to this state
        list_for_each_entry(sm_ev, &sm_st->pointed_event_ll, pointed_event_ll) {
            ori_sm_st = sm_ev->sm_state;
            printf("\tpointed event: %d/%p/%s, from state: %d/%p/%s, ev_fp:%p/%s\n", sm_ev->event, sm_ev, sm_ev->ev_name,
                            ori_sm_st->state, ori_sm_st, ori_sm_st->st_name,
                            sm_ev->action, sm_ev->action?sm_ev->action_fname:"");
        }
    }
    return 0;
}



2023年9月24日 星期日

Linux Kernel(25.1)- Gadget Configfs


這篇是gadget_configfs.txt的心得, 透過Dummy HCD的模擬, 就可以不用真的去連USH host才能驗證Gadget了.
首先把Dummy HCD與USB Gadget functions configurable through configf選成built-in了, 方便後面驗證, 下面就直接用例子說明
/ # lsusb 查看目前USB裝置, ID <Vendor ID>:<Device ID>
Bus 001 Device 001: ID 1d6b:0002 可以看到目前只有一組, 1B6D是Linux Foundation, 0002是2.0 root hub

/ # mount -t configfs none /sys/kernel/config/ 要把configfs掛起來才能開始設定gadget
/ # mount
rootfs on / type rootfs (rw,size=40392k,nr_inodes=10098)
tmpfs on /dev type tmpfs (rw,relatime,size=64k,mode=755)
devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000)
proc on /proc type proc (rw,relatime)
sysfs on /sys type sysfs (rw,relatime)
none on /sys/kernel/config type configfs (rw,relatime)

步驟一(Creating the gadgets) : 每一個Gadget都要建立自己的目錄, 並在其子目錄下做設定, 
我們就約定俗成取成g1吧
格式如下:
	$ mkdir /sys/kernel/config/usb_gadget/<gadget name>
/ # mkdir /sys/kernel/config/usb_gadget/g1 
/ # cd /sys/kernel/config/usb_gadget/g1

每個Gadget都需要有自己的VID與PID, 
格式如下:
	$ echo <VID> > idVendor
	$ echo <PID> > idProduct
/sys/kernel/config/usb_gadget/g1 # echo 0x1d6b > idVendor
/sys/kernel/config/usb_gadget/g1 # echo 0x0104 > idProduct

接著要為每個Gadget設定serial number, manufacturer和product strings
而這些設定會放置在strings底的語系的目錄下, 這裡0x409是英語系
格式如下:
	$ echo <serial number> > strings/0x409/serialnumber
	$ echo <manufacturer> > strings/0x409/manufacturer
	$ echo <product> > strings/0x409/product
/sys/kernel/config/usb_gadget/g1 # mkdir strings/0x409
/sys/kernel/config/usb_gadget/g1 # echo "Brook Technologies" > strings/0x409/manufacturer
/sys/kernel/config/usb_gadget/g1 # echo "Brook's Dummy Storage Gadget" > strings/0x409/product
/sys/kernel/config/usb_gadget/g1 # echo "12345678" > strings/0x409/serialnumber

步驟二(Creating the configurations) : 每個Gadget都會包含許多配置(configurations), 
這些configurations對應的目錄都要被建立,
格式如下:
	$ mkdir configs/<name>.<number>
每個configuration都需要自己的strings與語系, 比如
	$ mkdir configs/c.1/strings/0x409
	$ echo <configuration> > configs/c.1/strings/0x409/configuration
也有一些attributes如MaxPower需要被設定,    
/sys/kernel/config/usb_gadget/g1 # mkdir configs/c.1
/sys/kernel/config/usb_gadget/g1 # mkdir configs/c.1/strings/0x409
/sys/kernel/config/usb_gadget/g1 # echo 'Brook_Gadget' > configs/c.1/strings/0x409/configuration
/sys/kernel/config/usb_gadget/g1 # echo 250 > configs/c.1/MaxPower

步驟三 (Creating the functions) : 每個Gadget都會提供一些function, 每個function都要有對應的目錄
格式如下:
	$ mkdir functions/<name>.<instance name>
name還有對應的attribute可以參考Documentation/ABI/*/configfs-usb-gadget*
以下以mass_storage為範例, 並參考ABI/testing/configfs-usb-gadget-mass-storage
/sys/kernel/config/usb_gadget/g1 # mkdir functions/mass_storage.usb0
Mass Storage Function, version: 2009/09/11
LUN: removable file: (no medium)
/sys/kernel/config/usb_gadget/g1 # ls functions/mass_storage.usb0/
lun.0  stall
/sys/kernel/config/usb_gadget/g1 # ls functions/mass_storage.usb0/lun.0/
cdrom           inquiry_string  removable
file            nofua           ro

參數stall: 必須設為true.
/sys/kernel/config/usb_gadget/g1 # echo 1 > functions/mass_storage.usb0/stall

參數lun.0/removable: 是否可被移除
/sys/kernel/config/usb_gadget/g1 # echo 0 > functions/mass_storage.usb0/lun.0/removable

參數lun.0/ro: 是否唯讀
/sys/kernel/config/usb_gadget/g1 # echo 0 > functions/mass_storage.usb0/lun.0/ro

參數lun.0/file: 如果設定為lun.0/removable=0, 就要提供LUN的備份檔案路徑
/sys/kernel/config/usb_gadget/g1 # dd if=/dev/zero of=/mass.vfat bs=1M count=8
8+0 records in
8+0 records out
8388608 bytes (8.0MB) copied, 0.133141 seconds, 60.1MB/s
/sys/kernel/config/usb_gadget/g1 # mkfs.vfat /mass.vfat
/sys/kernel/config/usb_gadget/g1 # echo '/mass.vfat' > functions/mass_storage.usb0/lun.0/file

步驟四 (Associating the functions with their configurations) : 
格式如下:
	$ ln -s functions/<name>.<instance name> configs/<name>.<number>
    
/sys/kernel/config/usb_gadget/g1 # ln -s functions/mass_storage.usb0 configs/c.1/

步驟五 (Enabling the gadget) : 
基本上enable gadget就是把它跟UDC(USB Device Controller)做綁定. UDC可以在/sys/class/udc/找到
比如我的系統是"dummy_udc.0", 把把他echo 到UDC就可以了
/sys/kernel/config/usb_gadget/g1 # ls /sys/class/udc/
dummy_udc.0
/sys/kernel/config/usb_gadget/g1 # echo dummy_udc.0 > UDC
底下就是長出來的USB Disk了
usb 1-1: new high-speed USB device number 2 using dummy_hcd
usb 1-1: New USB device found, idVendor=1d6b, idProduct=0104, bcdDevice= 5.15
usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-1: Product: Brook's Dummy Storage Gadget
usb 1-1: Manufacturer: Brook Technologies
usb 1-1: SerialNumber: 12345678
usb-storage 1-1:1.0: USB Mass Storage device detected
scsi host0: usb-storage 1-1:1.0
scsi 0:0:0:0: Direct-Access     Linux    File-Stor Gadget 0515 PQ: 0 ANSI: 2
sd 0:0:0:0: Power-on or device reset occurred
sd 0:0:0:0: [sda] 16384 512-byte logical blocks: (8.39 MB/8.00 MiB)
sd 0:0:0:0: [sda] Write Protect is off
sd 0:0:0:0: [sda] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA
 sda:
sd 0:0:0:0: [sda] Attached SCSI disk

/sys/kernel/config/usb_gadget/g1 # lsusb
Bus 001 Device 001: ID 1d6b:0002
Bus 001 Device 002: ID 1d6b:0104
下次有機會再多介紹幾個gadget吧

    參考資料:
  • Documentation/usb/gadget_configfs.txt



2023年9月16日 星期六

Linux Kernel(11.2)- mdev.conf


busybox實作了mdev來處理動態更新/dev, 這篇文章主要是mdev.txt心得記錄.
以下幾個範例是在init script做init的範例,
Here's a typical code snippet from the init script:
[0] mount -t proc proc /proc
[1] mount -t sysfs sysfs /sys
[2] echo /sbin/mdev > /proc/sys/kernel/hotplug
[3] mdev -s

Alternatively, without procfs the above becomes:
[1] mount -t sysfs sysfs /sys
[2] sysctl -w kernel.hotplug=/sbin/mdev
[3] mdev -s
基本上不論如何都要先mount /sys才能開始做, mdev -s, 因為mdev主要就是靠讀取/sys/dev底下的資訊來建立相關的device node. 基本上kernel config要開CONFIG_UEVENT_HELPER才會有/proc/sys/kernel/hotplug (kernel.hotplug), 不過沒開就是在系統裝置異動時, 得自己手動執行mdev -s. 最後就是執行mdev -s建立相關的device node.

接著說明一下/etc/mdev.conf
The file has the format:
[-][envmatch]<device regex>     <uid>:<gid> <permissions>
[envmatch]@<maj[,min1[-min2]]>  <uid>:<gid> <permissions>
        $envvar=<regex>         <uid>:<gid> <permissions>
        
You can rename/move device nodes by using the next optional field.
 <device regex> <uid>:<gid> <permissions> [=path]
 
For example:
        hd[a-z][0-9]* 0:3 660

Mdev has an optional config file for controlling ownership/permissions of
device nodes if your system needs something more than the default root/root
660 permissions.

基本上mdev是採first match, 如果第一個rule比對成功, 就會套用該rule, 不然就往下一個去, 但是如果遇到"-"不論有沒有match, 都會往下一個去match去執行. 另外, 參數沒有給齊會用"0:0 600"當預設值.
下面的範例就是用"-"跟沒有"-"作範例, 可以看到"-"rule match, 也會繼續往下比
/ # cat /etc/mdev.conf
-tty0 1:1 0660 @/x.sh 就算tty0 match這個rule, 也會繼續執行下一個
tty0 1:1 0660 @/y.sh
tty1 1:1 0660 @/x.sh 如果tty1 match這個rule, 就會結束, 下一個就不會被執行了
tty1 1:1 0660 @/y.sh

/ # cat /x.sh
#!/bin/sh -x

# Redirect standard output to /dev/console
exec 1>/dev/kmsg

echo "start $0"
env

# Your script commands go here
echo "end $0"

/ # cat /y.sh
#!/bin/sh -x

# Redirect standard output to /dev/console
exec 1>/dev/kmsg

echo "start $0"
env

# Your script commands go here
echo "end $0"

/ # mdev -s
+ exec
+ echo 'start /x.sh'
start /x.sh
+ env
USER=root
ACTION=add
SHLVL=3
HOME=/
OLDPWD=/dev
MDEV=tty0 第一次呼叫
TERM=vt102
SUBSYSTEM=tty
PATH=/sbin:/usr/sbin:/bin:/usr/bin
SHELL=/bin/sh
PWD=/dev
+ echo 'end /x.sh'
end /x.sh
+ exec
+ echo 'start /y.sh'
start /y.sh
+ env
USER=root
ACTION=add
SHLVL=3
HOME=/
OLDPWD=/dev
MDEV=tty0 第二次呼叫
TERM=vt102
SUBSYSTEM=tty
PATH=/sbin:/usr/sbin:/bin:/usr/bin
SHELL=/bin/sh
PWD=/dev
+ echo 'end /y.sh'
end /y.sh
+ exec
+ echo 'start /x.sh'
start /x.sh
+ env
USER=root
ACTION=add
SHLVL=3
HOME=/
OLDPWD=/dev
MDEV=tty1 一次呼叫
TERM=vt102
SUBSYSTEM=tty
PATH=/sbin:/usr/sbin:/bin:/usr/bin
SHELL=/bin/sh
PWD=/dev
+ echo 'end /x.sh'
end /x.sh

下一個範例是rename或者放到/dev底下的子目錄, 語法是<device regex> <uid>:<gid> <permissions> [=path], 而如果要產在在某個子目錄下, 就在後面多加"/".
/ # rm /dev/tty* 刪除所有tty檔案後重新透過mdev -s建立 
/ # ls /dev/ 確認tty檔案都被砍掉
console          mmcblk0p2        ptyp5            urandom
cpu_dma_latency  mtd0             ptyp6            usbmon0
dri              mtd0ro           ptyp7            vcs
fb0              mtd1             ptyp8            vcs1
full             mtd1ro           ptyp9            vcs2
gpiochip0        mtdblock0        ptypa            vcsa
gpiochip1        mtdblock1        ptypb            vcsa1
gpiochip2        null             ptypc            vcsa2
gpiochip3        ptmx             ptypd            vcsu
hwrng            pts              ptype            vcsu1
input            ptyp0            ptypf            vcsu2
kmsg             ptyp1            random           zero
mem              ptyp2            rtc0
mmcblk0          ptyp3            snd
mmcblk0p1        ptyp4            ubi_ctrl
/ # cat /etc/mdev.conf
tty0 1:1 0660 =ttyBrook0 會把tty0 rename 成 ttyBrook0
tty.* 1:1 0660 =ttyBrook/ 把其他tty都建立在/dev/ttyBrook目錄下

/ # mdev -s
/ # ls /dev/
console          mmcblk0p2        ptyp5            ttyBrook0
cpu_dma_latency  mtd0             ptyp6            ubi_ctrl
dri              mtd0ro           ptyp7            urandom
fb0              mtd1             ptyp8            usbmon0
full             mtd1ro           ptyp9            vcs
gpiochip0        mtdblock0        ptypa            vcs1
gpiochip1        mtdblock1        ptypb            vcs2
gpiochip2        null             ptypc            vcsa
gpiochip3        ptmx             ptypd            vcsa1
hwrng            pts              ptype            vcsa2
input            ptyp0            ptypf            vcsu
kmsg             ptyp1            random           vcsu1
mem              ptyp2            rtc0             vcsu2
mmcblk0          ptyp3            snd              zero
mmcblk0p1        ptyp4            ttyBrook

/ # ls /dev/ttyBrook除了tty0其餘tty都在這子目錄下
tty      tty19    tty29    tty39    tty49    tty59    ttyAMA2  ttyp9
tty1     tty2     tty3     tty4     tty5     tty6     ttyAMA3  ttypa
tty10    tty20    tty30    tty40    tty50    tty60    ttyp0    ttypb
tty11    tty21    tty31    tty41    tty51    tty61    ttyp1    ttypc
tty12    tty22    tty32    tty42    tty52    tty62    ttyp2    ttypd
tty13    tty23    tty33    tty43    tty53    tty63    ttyp3    ttype
tty14    tty24    tty34    tty44    tty54    tty7     ttyp4    ttypf
tty15    tty25    tty35    tty45    tty55    tty8     ttyp5
tty16    tty26    tty36    tty46    tty56    tty9     ttyp6
tty17    tty27    tty37    tty47    tty57    ttyAMA0  ttyp7
tty18    tty28    tty38    tty48    tty58    ttyAMA1  ttyp8

這個範例,是在最後一個欄位加上"!", 代表不創建該node, 語法是<device regex> <uid>:<gid> <permissions> [!] [@|$|*<command>], 下面只會建立tty0, 其餘的tty都不會被建立
/ # cat /etc/mdev.conf
tty0 1:1 0660 =ttyBrook0 會把tty0 rename 成ttyBrook0
tty1 1:1 0660 ! @/x.sh 雖然不會建立tty1, 但是會去執行/x.sh
tty.* 1:1 0660 ! 其餘的tty都不建立了

/ # cat /x.sh
#!/bin/sh -x

# Redirect standard output to /dev/console
exec 1>/dev/kmsg

echo "start $0"
env

# Your script commands go here
echo "end $0"

/ # rm -rf /dev/tty*
/ # ls /dev/
console          mmcblk0p2        ptyp5            urandom
cpu_dma_latency  mtd0             ptyp6            usbmon0
dri              mtd0ro           ptyp7            vcs
fb0              mtd1             ptyp8            vcs1
full             mtd1ro           ptyp9            vcs2
gpiochip0        mtdblock0        ptypa            vcsa
gpiochip1        mtdblock1        ptypb            vcsa1
gpiochip2        null             ptypc            vcsa2
gpiochip3        ptmx             ptypd            vcsu
hwrng            pts              ptype            vcsu1
input            ptyp0            ptypf            vcsu2
kmsg             ptyp1            random           zero
mem              ptyp2            rtc0
mmcblk0          ptyp3            snd
mmcblk0p1        ptyp4            ubi_ctrl
/ # mdev -s
+ exec
+ echo 'start /x.sh'
start /x.sh
+ env
USER=root
ACTION=add
SHLVL=3
HOME=/
OLDPWD=/dev
MDEV=tty1 雖然不會建立tty1, 但是會去執行/x.sh
TERM=vt102
SUBSYSTEM=tty
PATH=/sbin:/usr/sbin:/bin:/usr/bin
SHELL=/bin/sh
PWD=/dev
+ echo 'end /x.sh'
end /x.sh
/ # ls /dev/
console          mmcblk0p2        ptyp5            ubi_ctrl
cpu_dma_latency  mtd0             ptyp6            urandom
dri              mtd0ro           ptyp7            usbmon0
fb0              mtd1             ptyp8            vcs
full             mtd1ro           ptyp9            vcs1
gpiochip0        mtdblock0        ptypa            vcs2
gpiochip1        mtdblock1        ptypb            vcsa
gpiochip2        null             ptypc            vcsa1
gpiochip3        ptmx             ptypd            vcsa2
hwrng            pts              ptype            vcsu
input            ptyp0            ptypf            vcsu1
kmsg             ptyp1            random           vcsu2
mem              ptyp2            rtc0             zero
mmcblk0          ptyp3            snd
mmcblk0p1        ptyp4            ttyBrook0只有ttyBrook0

這個範例是說明指令被執行的時機,'@'建立node之後執行, '$'移除node前執行, '*'等同'@'+'$', 這個我就想透過CONFIG_UEVENT_HELPER來demo比較有感覺
The special characters have the meaning:
        @ Run after creating the device.
        $ Run before removing the device.
        * Run both after creating and before removing the device.
/ # sysctl -a| grep hotplug 確認hotplug有被設定
kernel.hotplug = /sbin/mdev

/ # cat /etc/mdev.conf 
mmc.* 1:1 0660 @/x.sh 設定create node之後執行
/ # cat /x.sh
#!/bin/sh -x

# Redirect standard output to /dev/console
exec 1>/dev/kmsg

echo "start $0"
sleep 1
env
ls /dev/mmcblk*

# Your script commands go here
echo "end $0"

/ # ls /dev/mmcblk*
/dev/mmcblk0
/ # echo -e 'n\np\n1\n\n\nw' | fdisk /dev/mmcblk0 建立一個新分區

The number of cylinders for this disk is set to 32768.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)

Command (m for help): Partition type
   p   primary partition (1-4)
   e   extended
Partition number (1-4): First sector (16-2097151, default 16): Using default value 16
Last sector or +size{,K,M,G,T} (16-2097151, default 2097151): Using default value 2097151

Command (m for help): The partition table has been altered.
Calling ioctl() to re-read partition table
 mmcblk0: p1
/ # 
start /x.sh該scrip會在node建立後被執行
DEVNAME=mmcblk0p1
ACTION=add
SHLVL=2
HOME=/
SEQNUM=762
MAJOR=179
MDEV=mmcblk0p1
DEVPATH=/devices/platform/bus@40000000/bus@40000000:motherboard-bus@40000000/bus@40000000:motherboard-bus@40000000:iofpga@7,00000000/10005000.mmci/mmc_host/mmc0/mmc0:4567/block/mmcblk0/mmcblk0p1
SUBSYSTEM=block
PATH=/sbin:/bin:/usr/sbin:/usr/bin
DISKSEQ=3
MINOR=1
PARTN=1
PWD=/dev
DEVTYPE=partition
/dev/mmcblk0
/dev/mmcblk0p1
end /x.sh

/ # echo -e 'd\nw' | fdisk /dev/mmcblk0 刪除分區, 
因為mdev只有設定新增node之後執行, 所以移除分區不會執行/x.sh

The number of cylinders for this disk is set to 32768.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)

Command (m for help): Selected partition 1

Command (m for help): The partition table has been altered.
Calling ioctl() to re-read partition table
 mmcblk0:
/ # cat /etc/mdev.conf 修改移除node之後執行
mmc.* 1:1 0660 $/x.sh
/ # ls /dev/mmcblk* 底下沒有任何新分割區
/dev/mmcblk0
/ # echo -e 'n\np\n1\n\n\nw' | fdisk /dev/mmcblk0 建立新分區, 
但是mdev被設定移除才會執行/x.sh, 所以此時不會執行/x.sh

The number of cylinders for this disk is set to 32768.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)

Command (m for help): Partition type
   p   primary partition (1-4)
   e   extended
Partition number (1-4): First sector (16-2097151, default 16): Using default value 16
Last sector or +size{,K,M,G,T} (16-2097151, default 2097151): Using default value 2097151

Command (m for help): The partition table has been altered.
Calling ioctl() to re-read partition table
 mmcblk0: p1
/ # ls /dev/mmcblk*
/dev/mmcblk0    /dev/mmcblk0p1
/ # echo -e 'd\nw' | fdisk /dev/mmcblk0 移除分區

The number of cylinders for this disk is set to 32768.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)

Command (m for help): Selected partition 1

Command (m for help): The partition table has been altered.
Calling ioctl() to re-read partition table
 mmcblk0:
/ # 
start /x.sh 因為此時的mdev.conf被設定移除node後執行/x.sh, 所以這裡會執行/x.sh
DEVNAME=mmcblk0p1
ACTION=remove
SHLVL=2
HOME=/
SEQNUM=767
MAJOR=179
MDEV=mmcblk0p1
DEVPATH=/devices/platform/bus@40000000/bus@40000000:motherboard-bus@40000000/bus@40000000:motherboard-bus@40000000:iofpga@7,00000000/10005000.mmci/mmc_host/mmc0/mmc0:4567/block/mmcblk0/mmcblk0p1
SUBSYSTEM=block
PATH=/sbin:/bin:/usr/sbin:/usr/bin
DISKSEQ=3
MINOR=1
PARTN=1
PWD=/dev
DEVTYPE=partition
/dev/mmcblk0
/dev/mmcblk0p1
end /x.sh


  • busybox docs/mdev.txt
  • http://kernel.org/doc/pending/hotplug.txt
  • https://www.cnblogs.com/sky-heaven/p/5688092.html




熱門文章