2024年7月5日 星期五

Configuration of a minimal systemd setup in QEMU


本文記錄如何在 QEMU 環境中設定最小的 systemd init。基本上需要建立兩個必要的目標:sysinit.target 和 basic.target,以及用於登入的 getty@.service。
SRCROOT=/opt/armv7vet2hf/sysroots/
DSTROOT=initrd
SRCLIB=$SRCROOT/lib
DSTLIB=$DSTROOT/lib
SRCSYSDLIB=$SRCROOT/lib/systemd
DSTSYSDLIB=$DSTROOT/lib/systemd
SRCUSRLIB=$SRCROOT/usr/lib
DSTUSRLIB=$DSTROOT/usr/lib

rm -rf $DSTROOT
mkdir -p $DSTROOT/bin $DSTROOT/usr/lib $DSTROOT/lib/systemd/system $DSTROOT/etc/systemd/system

# copy libraries
cp -a $SRCLIB/libselinux.so* $DSTLIB
cp -a $SRCLIB/libmount.so* $DSTLIB
cp -a $SRCLIB/libaudit.so* $DSTLIB
cp -a $SRCLIB/libc.so* $DSTLIB
cp -a $SRCLIB/ld-linux-armhf.so* $DSTLIB
cp -a $SRCLIB/libblkid.so* $DSTLIB
cp -a $SRCLIB/libcap.so* $DSTLIB
cp -a $SRCLIB/libm.so* $DSTLIB
cp -a $SRCLIB/libpcre.so* $DSTLIB
cp -a $SRCLIB/libcap-ng.so* $DSTLIB

cp -a $SRCUSRLIB/libacl.so* $DSTUSRLIB
cp -a $SRCUSRLIB/libcrypt.so* $DSTUSRLIB
cp -a $SRCUSRLIB/liblzma.so* $DSTUSRLIB
cp -a $SRCUSRLIB/libattr.so* $DSTUSRLIB

cp -a $SRCSYSDLIB/systemd $DSTSYSDLIB
cp -a $SRCSYSDLIB/libsystemd-shared* $DSTSYSDLIB
install -m 555 busybox-build/busybox $DSTROOT/bin/


# Create basic.target
cat << EOF > $DSTSYSDLIB/system/basic.target
[Unit]
Description=Basic System
EOF

# Create sysinit.target
cat << EOF > $DSTSYSDLIB/system/sysinit.target
[Unit]
Description=System Initialization
DefaultDependencies=no
EOF

# Create getty@tty1.service
cat << EOF > $DSTSYSDLIB/system/getty@.service
[Unit]
Description=Getty on %I
ConditionPathExists=/dev/%I

[Service]
ExecStart=-/sbin/getty 115200 %I
Restart=always

[Install]
WantedBy=basic.target rescue.target
EOF


chmod 644 -R $DSTSYSDLIB/system/*

cat << EOF > $DSTROOT/init
#!/bin/busybox sh
## Mount essential filesystems
/bin/busybox mkdir -p /proc /sys /dev /home /run/systemd/journal /tmp /var /mnt /sbin /usr/bin /usr/sbin /etc/systemd/system/basic.target.wants /etc/systemd/system/rescue.target.wants /etc/systemd/system/default.target.wants
/bin/busybox --install -s
ln -sf /lib/systemd/systemd /sbin/init

## Set the path for BusyBox applets if using BusyBox
export PATH=/sbin:/bin:/usr/sbin:/usr/bin
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mkdir -p /dev/pts
mount -t devpts none /dev/pts
## Ensure the getty@ttyAMA0.service file is linked in the correct place
ln -sf /lib/systemd/system/getty@.service /etc/systemd/system/basic.target.wants/getty@ttyAMA0.service
# create default password
echo "root:vnpTT1BZdW1/s:0:0:root:/root:/bin/sh" > /etc/passwd
echo "root:x:0:" > /etc/group

# Ensure systemd can find its units
ln -sf /lib/systemd/system/basic.target /lib/systemd/system/default.target

# Start systemd
exec /sbin/init
EOF
chmod +x $DSTROOT/init

fakeroot bash -c "cd linux && ./usr/gen_initramfs.sh ../$DSTROOT -o ../initrd-arm.img"



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



熱門文章