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




2023年9月15日 星期五

Linux Kernel(24.1)- fdisk Multimedia Card


這章節透過fdisk來了解底層的kernel相關的訊息, 為了方便學習, 這裡會使用qemu來模擬掛載eMMC.
首先會透過qemu-img create創建一個1GByte的eMMC, 並透過qemu掛載起來,
[brook@:~/Projects/qemu]$ qemu-img create -f qcow2 emmc_image.qcow2 1G
Formatting 'emmc_image.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zli6
[brook@:~/Projects/qemu]$ qemu-img info emmc_image.qcow2
image: emmc_image.qcow2
file format: qcow2
virtual size: 1 GiB (1073741824 bytes)
disk size: 196 KiB
cluster_size: 65536
Format specific information:
    compat: 1.1
    compression type: zlib
    lazy refcounts: false
    refcount bits: 16
    corrupt: false
    extended l2: false
[brook@:~/Projects/qemu]$ qemu-system-arm -smp cpus=4 -s \
    -M vexpress-a9 -m 128M -kernel ./linux/arch/arm/boot/zImage \
    -dtb ./linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
    -initrd ./initrd-arm.img -nographic -append "console=ttyAMA0" \
    -drive id=mysdcard,if=none,format=qcow2,file=emmc_image.qcow2 \
    -device sd-card,drive=mysdcard


進入qemu之後, 會長出/dev/mmcblk0, 可以看到就是當時候的1GB, 不論是透過fdisk或是/proc/partitions都可以看出這是一個未經分割的eMMC, 隨後透過fdisk分割成兩個512MB的分區, 寫入離開fdisk之後, 就可以看到/proc/partition有多出兩個partition, 對應的/dev也會長出mmcblk0p1與mmcblk0p2, 沒看到可以用/sbin/mdev -s再掃一次
/ # fdisk -l /dev/mmcblk0
Disk /dev/mmcblk0: 1024 MB, 1073741824 bytes, 2097152 sectors
32768 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes

/ # cat /proc/partitions 

major minor  #blocks  name

  31        0     131072 mtdblock0
  31        1      32768 mtdblock1
 179        0    1048576 mmcblk0
/ # fdisk /dev/mmcblk0
Device contains neither a valid DOS partition table, nor Sun, SGI, OSF or GPT disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that the previous content
won't be recoverable.


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): m
Command Action
a       toggle a bootable flag
b       edit bsd disklabel
c       toggle the dos compatibility flag
d       delete a partition
l       list known partition types
n       add a new partition
o       create a new empty DOS partition table
p       print the partition table
q       quit without saving changes
s       create a new empty Sun disklabel
t       change a partition's system id
u       change display/entry units
v       verify the partition table
w       write table to disk and exit
x       extra functionality (experts only)

Command (m for help): n
Partition type
   p   primary partition (1-4)
   e   extended
p
Partition number (1-4): 1
First sector (16-2097151, default 16):使用default, 直接按下enter
Using default value 16
Last sector or +size{,K,M,G,T} (16-2097151, default 2097151): +512M
Command (m for help): n
Partition type
   p   primary partition (1-4)
   e   extended
p
Partition number (1-4): 2
First sector (1048592-2097151, default 1048592):使用default, 直接按下enter
Using default value 1048592
Last sector or +size{,K,M,G,T} (1048592-2097151, default 2097151):使用default, 直接按下enter
Using default value 2097151

Command (m for help): p
Disk /dev/mmcblk0: 1024 MB, 1073741824 bytes, 2097152 sectors
32768 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes
Device       Boot StartCHS    EndCHS        StartLBA     EndLBA    Sectors  Size Id Type
/dev/mmcblk0p1    0,1,1       1023,3,16           16    1048591    1048576  512M 83 Linux
/dev/mmcblk0p2    1023,3,16   1023,3,16      1048592    2097151    1048560  511M 83 Linux

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table
 mmcblk0: p1 p2
/ # cat /proc/partitions
major minor  #blocks  name

  31        0     131072 mtdblock0
  31        1      32768 mtdblock1
 179        0    1048576 mmcblk0
 179        1     524288 mmcblk0p1
 179        2     524280 mmcblk0p2

/ # ls -al /dev/mmcblk0p1
brw-rw----    1 0        0         179,   1 Sep  2 06:13 /dev/mmcblk0p1
/ # ls -al /dev/mmcblk0p2
brw-rw----    1 0        0         179,   2 Sep  2 06:13 /dev/mmcblk0p2

Cylinder-head-sector (CHS) 是早期硬碟定址的方式, head就是讀寫頭, 每個磁盤(platter)兩面各有一個讀寫頭, 而一個又一個的同心圓就是track, 一個track被切割成若干個sector, 早期每個sector大小約512 byte, Cylinder是把所有磁盤上相同同心圓的總稱. 可以參考Wiki的圖片與解說
而後來改用LBA(Logical Block Addressing)取代了CHS, 其轉換公式如下,
LBA = (Cylinder × HPC + Head) × SPT + (Sector − 1)
Cylinder = LBA ÷ (HPC × SPT)
Head = (LBA ÷ SPT) mod HPC
S = (LBA mod SPT) + 1

HPC(Header Per Cylinder=4)與SPT(Sector Per Track=16)都是由driver回傳.以上述/dev/mmcblk0p1的StartLBA=16, 是因為offset就是16, EndLBA=1048591= (Cylinder=32768 × HPC=4 + Head) × SPT=16 + (Sector − 1), 這些轉換目前已經沒有太大意義了, 只要有概念就可以了

當執行完前面的fdisk作分割之後, 其實fdisk就會將partition table寫入/dev/mmcblk0前面的LBA0中, 也就是我們所謂的 Master Boot Record(MBR), 以下就是MBR的格式
MnemonicByte OffsetByte Length Description
BootCode0440x86 code used on a non-UEFI system to select an MBR par tition record and load the first logical block of that partition. This code shall not be executed on UEFI systems.
Unique MBRDisk Signature4404Unique Disk Signature This may be used by the OS to identify the disk from other disks in the system. This value is always written by the OS and is never written by EFI firmware
Unknown4442Unknown. This field shall not be used by UEFI firmware
Partition Record44616*4Array of four legacy MBR partition records
Signature5102Set to 0xAA55(i.e., byte 510 contains 0x55 and byte 511 contains 0xAA).

以下就是Partition Record的格式
MnemonicByte OffsetByte Length Description
BootIndicator01 0x80 indicates that this is the bootable legacy partition. Other values indicate that this is not a bootable legacy partition. This field shall not be used by UEFI firmware
StartingCHS13Start of partition in CHS address format. This field shall not be used by UEFI firmware
OSType41Type of partition
EndingCHS53End of partition in CHS address format. This field shall not be used by UEFI firmware.
StartingLBA84Starting LBA of the partition on the disk. This field is used by UEFI firmware to determine the start of the partition.
SizeInLBA124Size of the partition in LBA units of logical blocks. This field is used by UEFI firmware to determine the size of the partition

以下是常見的OS Type, 其餘可以參考Partition types
IDName
0x00Empty (Unused)
0x01FAT12 (DOS)
0x05DOS 3.3+ Extended Partition
0x07NTFS (Windows)
0x82Linux SWAP
0x83Linux
0x8ELinux LVM
0xEEGPT Protective Partition

以下的sample code分成兩部分, dump_geometry()與dump_mbr(), dump_geometry()主要是透過IOCTL取得MMC的物理資訊, 也就是Cylinder-head-sector (CHS), dump_mbr()主要是取得partition table資訊
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <linux/fs.h>		// Include the header file for ioctl and BLKSSZGET
#include <linux/hdreg.h>

#define MAX_SECTOR_SIZE 512
#pragma pack(push, 1)
static unsigned char MBRbuffer[MAX_SECTOR_SIZE];
struct PartitionEntry {
    uint8_t boot_ind;
    uint8_t chs_start[3];
    uint8_t type;
    uint8_t chs_end[3];
    uint32_t lba_start;
    uint32_t sectors;
};

// Structure to represent the Master Boot Record
struct MBR {
    uint8_t bootstrap_code[446];
    struct PartitionEntry partitions[4];
    uint16_t signature;
};
#pragma pack(pop)		// Restore default packing

static int sec_size = 0;
static struct hd_geometry geo;
static int dump_geometry(int fd)
{
    unsigned long long sz;
    if (ioctl(fd, BLKSSZGET, &sec_size) < 0) {
	printf("get BLKSSZGET failed\n");
	return 1;
    }

    if (ioctl(fd, HDIO_GETGEO, &geo) < 0) {
	// Print the obtained geometry information
	printf("%s(#%d): Cylinders: %d\n", __FUNCTION__, __LINE__, geo.cylinders);
	printf("%s(#%d): Heads: %d\n", __FUNCTION__, __LINE__, geo.heads);
	printf("%s(#%d): Start: %lu\n", __FUNCTION__, __LINE__, (unsigned long) geo.start);
	return 1;
    }
    sz = geo.cylinders * geo.heads * geo.sectors * sec_size;
    printf("%d MB, %llu bytes, %lu sectors\n", sz / (1024 * 1024), sz, geo.sectors);
    printf("%lu cylinders, %d heads, %d sectors/track\n", geo.cylinders, geo.heads, geo.sectors);
    printf("Units: sectors %d bytes\n\n", sec_size);
    return 0;
}

static void set_hsc(unsigned long lba, unsigned long *h, unsigned long *s, unsigned long *c)
{
    if ((lba / (geo.sectors * geo.heads) > 1023))
	lba = geo.heads * geo.sectors * 1024 - 1;
    *s = (lba % geo.sectors) + 1;
    *h = (lba / geo.sectors) % geo.heads;
    *c = (lba / geo.sectors) / geo.heads;
}


static int dump_mbr(int fd, char const * const prefix)
{
    struct MBR *mbr;
    struct PartitionEntry *p;
    ssize_t bytes_read;
    unsigned long lba, sc, sh, ss, ec, eh, es;

    bytes_read = read(fd, MBRbuffer, sizeof(MBRbuffer));
    if (bytes_read != 512) {
	printf("read failed: %d\n", bytes_read);
	return 1;
    }

    mbr = (struct MBR *) MBRbuffer;
    if (mbr->signature != 0xAA55) {
	printf("invalid MBR. signature:%04x/ M[510]:%02x, M[511]:%02x\n", mbr->signature, MBRbuffer[510], MBRbuffer[511]);
	return 1;
    }

    for (int i = 0; i < 4; i++) {
	p = &(mbr->partitions[i]);
	if (p->sectors == 0) {
	    continue;
	}
	lba = p->lba_start;
	set_hsc(lba, &sh, &ss, &sc);

	lba = p->lba_start + p->sectors - 1;
	set_hsc(lba, &eh, &es, &ec);

	printf("Device\t\t Boot\t StartCHS\t EndCHS\t StartLBA\t EndLBA\t Sectors\n");
	printf("%sp%d \t %c \t %d,%d,%d \t\t %d,%d,%d \t %d\t %d \t %lu \t %dM \t %x\n",
	       prefix, i, (p->boot_ind & 0x80) ? '*' : ' ', sc, sh, ss, ec, eh, es,
	       p->lba_start, p->lba_start + p->sectors - 1, p->sectors, (p->sectors * sec_size) / (1024 * 1024), p->type);
    }
}

static int sys_check(int argc, char *argv[])
{
    struct MBR *mbr;
    struct PartitionEntry *p;

    if (argc < 2) {
	printf("./%s <dev_name>\n", argv[0]);
	exit(-1);
    }

    if (sizeof(*mbr) != 512) {
	printf("invalid mbr size:%d\n", sizeof(*mbr));
	exit(-1);
    }

    if (sizeof(*p) != 16) {
	printf("invalid mbr size:%d\n", sizeof(*p));
	exit(-1);
    }
}


int main(int argc, char *argv[])
{
    const char *dev = argv[1];	// Replace with your device path
    struct MBR *mbr;
    struct PartitionEntry *p;
    int fd, sec_size = 0;

    sys_check(argc, argv);
    fd = open(dev, O_RDONLY);
    if (fd == -1) {
	printf("Error opening device %s failed\n", dev);
	return 1;
    }

    printf("Disk %s: ", dev);
    dump_geometry(fd);
    dump_mbr(fd, dev);

    close(fd);
    return 0;
}

以下為sample code執行結果
/ # /list-part /dev/mmcblk0
Disk /dev/mmcblk0: 1073741824 MB, 1024 bytes, 1073741824 sectors
32768 cylinders, 4 heads, 16 sectors/track
Units: sectors 512 bytes

Device          Boot   StartCHS   EndCHS      StartLBA   EndLBA    Sectors
/dev/mmcblk0p0   *     0,1,1      1023,3,16       16      1048591  1048576 512M 83
Device          Boot   StartCHS   EndCHS      StartLBA   EndLBA    Sectors
/dev/mmcblk0p1         1023,3,16  1023,3,16   1048592     2097151  1048560 511M 83



參考資料:
  • https://en.wikipedia.org/wiki/Cylinder-head-sector, Cylinder-head-sector
  • Documentation/ioctl/hdio.txt, Summary of HDIO_ ioctl calls
  • busybox/blob/master/util-linux/fdisk.c, busybox fdisk
  • https://uefi.org/sites/default/files/resources/UEFI_Spec_2_10_Aug29.pdf, CH5 - GUID PARTITION TABLE (GPT) DISK LAYOUT




熱門文章