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

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




2023年8月4日 星期五

Linux Kernel(21.1)- ID Allocation


如同ID Allocation的Overview提到的, kernel提供了對應的一些API, 用以產生與維護identifiers (IDs), 舉凡file descriptor, process IDs, device instance number等等. IDR主要多了ID與pointer的對用能力, 而IDA就是單純的分配ID, 本章透過簡單的程式碼讓大家能瞭解與使用IDA.

首先, 該範例是個簡易的kernel module, 透過DEFINE_IDA(my_ida)宣告一個my_ida變數, 這是我們, 並透過read file operation去取得一個新的ID(ida_simple_get), 在write file operation中透過寫入特定ID移除該ID(ida_simple_remove), 最後在移除kernel module時, 使用ida_destroy(struct ida * ida)把所有的IDA resource都釋放, 不然會造成memory leak.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/idr.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h> // Required for copy_from_user

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Brook");
MODULE_DESCRIPTION("Kernel module to demo IDA");
MODULE_VERSION("0.1");

static DEFINE_IDA(my_ida);

static ssize_t ida_demo_read(struct file *file, char *buf, size_t count, loff_t *ppos)
{
    int id, len;
    char tmp_buf[10];

    id = ida_simple_get(&my_ida, 0, 0, GFP_KERNEL);
    if (id >= 0) {
        printk(KERN_INFO "IDR Demo: Successfully allocated ID: %d\n", id);
    } else {
        printk(KERN_ERR "IDR Demo: Failed to allocate ID\n");
        return -ENOMEM;
    }

    len = snprintf(tmp_buf, sizeof(tmp_buf), "%d", id);
    if (len < 0) {
        return -EINVAL;
    }

    if (copy_to_user(buf, tmp_buf, len)) {
        return -EFAULT;
    }

    return 0;
}

static ssize_t
ida_demo_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    char tmp_buf[20];
    int id;

    if (count >= sizeof(tmp_buf))
        return -EINVAL;

    if (copy_from_user(tmp_buf, buf, count))
        return -EFAULT;

    tmp_buf[count] = '\0';
    // Convert the input string to an integer
    if (kstrtoint(tmp_buf, 10, &id)) {
        printk(KERN_ERR "invalid ID: %s\n", tmp_buf);
        return -EINVAL;
    }

    printk(KERN_INFO "remove ID %d\n", id);
    ida_simple_remove(&my_ida, id);

    return count;
}

// Define a file operation structure for IDA access
static struct file_operations ida_fops = {
    .open = simple_open,
    .read = ida_demo_read,
    .write = ida_demo_write,
    .llseek = default_llseek,
};

static int __init ida_demo_init(void)
{
    struct proc_dir_entry *proc_entry;

    printk(KERN_INFO "IDR Demo: Initializing module\n");

    // Create a file entry to invoke 'ida'
    proc_entry = proc_create("ida", S_IRUGO, NULL, &ida_fops);
    if (!proc_entry) {
        printk(KERN_ERR "Failed to create sysfs entry for 'ida'\n");
        return -ENOMEM;
    }
    return 0;
}

static void __exit ida_demo_exit(void)
{
    printk(KERN_INFO "IDR Demo: Exiting module\n");
    ida_destroy(&my_ida);
}

module_init(ida_demo_init);
module_exit(ida_demo_exit);


簡易的Makefile
KDIR ?= /build/brook/Projects/qemu/linux/
# Modules which are included in the kernel are installed in the
# directory:
#       /lib/modules/$(KERNELRELEASE)/kernel/
# And external modules are installed in:
#       /lib/modules/$(KERNELRELEASE)/extra/
#
# INSTALL_MOD_PATH
# A prefix can be added to the
#       installation path using the variable INSTALL_MOD_PATH:
#
#       $ make INSTALL_MOD_PATH=/frodo modules_install
#       => Install dir: /frodo/lib/modules/$(KERNELRELEASE)/kernel/
export INSTALL_MOD_PATH=/build/brook/Projects/qemu/initrd-arm
obj-m := ida_demo.o
ida_demo-y := ida_main.o

modules modules_install clean:
 $(MAKE) -C $(KDIR) M=$$PWD $@


這是在QEMU下執行的結果, 會先產生ID 0/1/2, 然後移除1, 接著再產生的ID就會把1生出來, 再來就是3了, 所以透過IDA, 可以幫user管理ID(唯一的編號)
/ # uname -a
Linux (none) 5.4.0+ #6 SMP Tue Jan 3 08:39:24 CST 2023 armv7l GNU/Linux
/ # insmod /lib/modules/5.4.0+/extra/ida_demo.ko
ida_demo: loading out-of-tree module taints kernel.
IDR Demo: Initializing module
/ # cat /proc/ida
IDR Demo: Successfully allocated ID: 0
/ # cat /proc/ida
IDR Demo: Successfully allocated ID: 1
/ # cat /proc/ida
IDR Demo: Successfully allocated ID: 2
/ # echo 1 > /proc/ida
remove ID 1
/ # cat /proc/ida
IDR Demo: Successfully allocated ID: 1
/ # cat /proc/ida
IDR Demo: Successfully allocated ID: 3



  • https://www.kernel.org/doc/html/v5.4/core-api/idr.html, ID Allocation




2023年1月1日 星期日

Table Of Content for tag "Linux - kernel"


Linux Kernel(1)- Linux Module簡介
Linux Modules(1.1)module parameters

Linux Kernel(2)- register char device
Linux Kernel(2.1)- MAJRO NUMBER RESERVED FOR DYNAMIC ASSIGNMENT

Linux Kernel(3)- procfs
Linux Kernel(3.1)- procfs之vector方式寫入
Linux Kernel(3.2)- procfs之symlink與mkdir

Linux Kernel(4)- seq_file
Linux Kernel(4.1)- seq_file之範例(fp/proc/devices.c)
Linux Kernel(4.2)- seq_file之single page

Linux Kernel(5)- ioctl

Linux Kernel(6)- miscdev

Linux Kernel(7)- timing
Linux Kernel(7.1)- timer
Linux Modules(7.2)- tasklet
Linux Modules(7.3)- work queue

Linux Kernel(8)- Notification
Linux Kernel(8.1)- Notifier機制剖析

Linux Kernel(9)- Kthread

Linux Kernel(10)- MTD/Memory Technology Device
Linux Kernel(10.1)- drivers/mtd/devices/mtdram.c
Linux Kernel(10.2)- mtd partitions
Linux Kernel(10.3)- Command line partition table parsing
Linux Kernel(10.3.1)- Command line partition table parsing for Kernel 4.19

Linux Kernel(11)- sysfs and device node
Linux Kernel(11.1)- sysfs and hotplug
Linux Kernel(11.2)- mdev.conf

Linux Kernel(12)- netfilter
Linux Kernel(12.1)- netfilter機制之初探

Linux Kernel(13)- syscall

Linux Kernel(14)- Kernel Synchronization
Linux Modules(14.1)- Read Copy Update

Linux Kernel(15)- Platform Devices
Linux Kernel(15.1)- platform_driver_register()之如何调用driver.probe()
Linux Kernel(15.2)- platform_device_register()之如何调用driver.probe()
Linux Kernel(15.3)- The Linux usage model for device tree data

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

Linux Kernel(17)- Device Tree
Linux Kernel(17.1)- Basic Device Tree syntax
Linux Kernel(17.2)- Common Device Tree API

Linux Kernel(18)- Virtual File System
Linux Kernel(18.1)- My First Filesystem
Linux Kernel(18.2)- SysCall mount

Linux Kernel(19)- General Purpose Input/Output
Linux Kernel(19.1)- /sys/class/gpio usage

Linux Kernel(20)- Input device
Linux Kernel(20.1)- Input device user program
Linux Kernel(20.2)- uinput module
Linux Kernel(20.3)- Creating an input device driver

Linux Kernel(21)- ID Allocation
Linux Kernel(21.1)- ID Allocation
Linux Kernel(21.2)- radix tree API
Linux Kernel(21.3)- radix tree implementation

Linux Kernel(22)- Linux Socket
Linux Kernel(22.1)- My Socket Domain and Protocol

Linux Kernel(23)- SKB


Linux Kernel(24)- Multimedia Card
Linux Kernel(24.1)- fdisk Multimedia Card

Linux Kernel(25)- USB(Universal Serial Bus)
Linux Kernel(25.1)- Gadget Configfs




2022年12月31日 星期六

Linux Kernel(19.1)- /sys/class/gpio usage


要能使用/sys/class/gpio就要先開啟相關kernel config如下, 如提示所說, 該ABI已經棄用, 改由character device /dev/gpiochipN取代, 不過這裡還是會簡單交代一下相關資訊, 作為紀錄.
  │ CONFIG_GPIO_SYSFS:                                                                             │
  │                                                                                                │
  │ Say Y here to add the legacy sysfs interface for GPIOs.                                        │
  │                                                                                                │
  │ This ABI is deprecated. If you want to use GPIO from userspace,                                │
  │ use the character device /dev/gpiochipN with the appropriate                                   │
  │ ioctl() operations instead. The character device is always                                     │
  │ available.                                                                                     │
  │                                                                                                │
  │ Symbol: GPIO_SYSFS [=y]                                                                        │
  │ Type  : bool                                                                                   │
  │ Prompt: /sys/class/gpio/... (sysfs interface)                                                  │
  │   Location:                                                                                    │
  │     -> Device Drivers                                                                          │
  │       -> GPIO Support (GPIOLIB [=y])                                                           │
  │   Defined at drivers/gpio/Kconfig:61                                                           │
  │   Depends on: GPIOLIB [=y] && SYSFS [=y]                                                       │                                   

在設定玩kernel config之後, 重新編譯kernel, 就可以看見"/sys/class/gpio"目錄, 裡面幾個重要的檔案,
export Userspace may ask the kernel to export control of a GPIO to userspace by writing its number to this file.
unexport Reverses the effect of exporting to userspace.

"echo 19 > /sys/class/gpio/export" 後, 目錄/sys/class/gpio/gpio19/就會被創建出來, 裡面會包含
direction reads as either "in" or "out"
value reads as either 0 (low) or 1 (high)
edge reads as either "none", "rising", "falling", or "both"
active_low reads as either 0 (false) or 1 (true)

此外還可以透過debugfs來檢查當前的GPIO設定
/ # cat /sys/kernel/debug/gpio
gpiochip3: GPIOs 499-499, parent: platform/basic-mmio-gpio.3.auto, sys_flash:

gpiochip2: GPIOs 500-501, parent: platform/basic-mmio-gpio.2.auto, sys_mci:
 gpio-500 (                    |cd                  ) in  lo
 gpio-501 (                    |wp                  ) in  lo

gpiochip1: GPIOs 502-509, parent: platform/basic-mmio-gpio.1.auto, sys_led:
 gpio-502 (                    |?                   ) out lo
 gpio-503 (                    |?                   ) out lo
 gpio-504 (                    |?                   ) out lo
 gpio-505 (                    |?                   ) out lo
 gpio-506 (                    |?                   ) out hi
 gpio-507 (                    |?                   ) out lo
 gpio-508 (                    |?                   ) out lo
 gpio-509 (                    |?                   ) out lo

gpiochip0: GPIOs 510-511, parent: platform/10000000.sysreg, 10000000.sysreg:

/ # ls -al /sys/class/gpio/
total 0
drwxr-xr-x    2 0        0                0 Jan  3 00:40 .
drwxr-xr-x   36 0        0                0 Jan  3 00:40 ..
--w-------    1 0        0             4096 Jan  3 00:40 export
lrwxrwxrwx    1 0        0                0 Jan  3 00:40 gpiochip499 -> ../../devices/platform/10000000.sysreg/basic-mmio-gpio.3.auto/gpio/gpiochip499
lrwxrwxrwx    1 0        0                0 Jan  3 00:40 gpiochip500 -> ../../devices/platform/10000000.sysreg/basic-mmio-gpio.2.auto/gpio/gpiochip500
lrwxrwxrwx    1 0        0                0 Jan  3 00:40 gpiochip502 -> ../../devices/platform/10000000.sysreg/basic-mmio-gpio.1.auto/gpio/gpiochip502
lrwxrwxrwx    1 0        0                0 Jan  3 00:40 gpiochip510 -> ../../devices/platform/10000000.sysreg/gpio/gpiochip510
--w-------    1 0        0             4096 Jan  3 00:40 unexport

接下來export 510, 511, 然後在unexport 511, 設定510, 相關操作如下
/ # echo 510 > /sys/class/gpio/export
/ # echo 511 > /sys/class/gpio/export
/ # ls -al /sys/class/gpio/
total 0
drwxr-xr-x    2 0        0                0 Jan  3 00:40 .
drwxr-xr-x   36 0        0                0 Jan  3 00:40 ..
--w-------    1 0        0             4096 Jan  3 00:43 export
lrwxrwxrwx    1 0        0                0 Jan  3 00:42 gpio510 -> ../../devices/platform/10000000.sysreg/gpiochip0/gpio/gpio510
lrwxrwxrwx    1 0        0                0 Jan  3 00:43 gpio511 -> ../../devices/platform/10000000.sysreg/gpiochip0/gpio/gpio511
lrwxrwxrwx    1 0        0                0 Jan  3 00:40 gpiochip499 -> ../../devices/platform/10000000.sysreg/basic-mmio-gpio.3.auto/gpio/gpiochip499
lrwxrwxrwx    1 0        0                0 Jan  3 00:40 gpiochip500 -> ../../devices/platform/10000000.sysreg/basic-mmio-gpio.2.auto/gpio/gpiochip500
lrwxrwxrwx    1 0        0                0 Jan  3 00:40 gpiochip502 -> ../../devices/platform/10000000.sysreg/basic-mmio-gpio.1.auto/gpio/gpiochip502
lrwxrwxrwx    1 0        0                0 Jan  3 00:40 gpiochip510 -> ../../devices/platform/10000000.sysreg/gpio/gpiochip510
--w-------    1 0        0             4096 Jan  3 00:40 unexport

/ # echo 511 > /sys/class/gpio/unexport
/ # ls -al /sys/class/gpio/
total 0
drwxr-xr-x    2 0        0                0 Jan  3 00:40 .
drwxr-xr-x   36 0        0                0 Jan  3 00:40 ..
--w-------    1 0        0             4096 Jan  3 00:43 export
lrwxrwxrwx    1 0        0                0 Jan  3 00:42 gpio510 -> ../../devices/platform/10000000.sysreg/gpiochip0/gpio/gpio510
lrwxrwxrwx    1 0        0                0 Jan  3 00:40 gpiochip499 -> ../../devices/platform/10000000.sysreg/basic-mmio-gpio.3.auto/gpio/gpiochip499
lrwxrwxrwx    1 0        0                0 Jan  3 00:40 gpiochip500 -> ../../devices/platform/10000000.sysreg/basic-mmio-gpio.2.auto/gpio/gpiochip500
lrwxrwxrwx    1 0        0                0 Jan  3 00:40 gpiochip502 -> ../../devices/platform/10000000.sysreg/basic-mmio-gpio.1.auto/gpio/gpiochip502
lrwxrwxrwx    1 0        0                0 Jan  3 00:40 gpiochip510 -> ../../devices/platform/10000000.sysreg/gpio/gpiochip510
--w-------    1 0        0             4096 Jan  3 00:43 unexport

/ # grep "" /sys/class/gpio/gpio510/*
/sys/class/gpio/gpio510/active_low:0
/sys/class/gpio/gpio510/direction:in
/sys/class/gpio/gpio510/value:0

/ # echo out > /sys/class/gpio/gpio510/direction
/ # grep "" /sys/class/gpio/gpio510/*
/sys/class/gpio/gpio510/active_low:0
/sys/class/gpio/gpio510/direction:out
/sys/class/gpio/gpio510/value:0

/ # cat /sys/kernel/debug/gpio
gpiochip3: GPIOs 499-499, parent: platform/basic-mmio-gpio.3.auto, sys_flash:

gpiochip2: GPIOs 500-501, parent: platform/basic-mmio-gpio.2.auto, sys_mci:
 gpio-500 (                    |cd                  ) in  lo
 gpio-501 (                    |wp                  ) in  lo

gpiochip1: GPIOs 502-509, parent: platform/basic-mmio-gpio.1.auto, sys_led:
 gpio-502 (                    |?                   ) out lo
 gpio-503 (                    |?                   ) out lo
 gpio-504 (                    |?                   ) out lo
 gpio-505 (                    |?                   ) out lo
 gpio-506 (                    |?                   ) out hi
 gpio-507 (                    |?                   ) out hi
 gpio-508 (                    |?                   ) out lo
 gpio-509 (                    |?                   ) out lo

gpiochip0: GPIOs 510-511, parent: platform/10000000.sysreg, 10000000.sysreg:
 gpio-510 (                    |sysfs               ) out lo

/ # grep "" /sys/class/gpio/gpiochip*/*
/sys/class/gpio/gpiochip499/base:499
/sys/class/gpio/gpiochip499/label:sys_flash
/sys/class/gpio/gpiochip499/ngpio:1
/sys/class/gpio/gpiochip500/base:500
/sys/class/gpio/gpiochip500/label:sys_mci
/sys/class/gpio/gpiochip500/ngpio:2
/sys/class/gpio/gpiochip502/base:502
/sys/class/gpio/gpiochip502/label:sys_led
/sys/class/gpio/gpiochip502/ngpio:8
/sys/class/gpio/gpiochip510/base:510
/sys/class/gpio/gpiochip510/label:10000000.sysreg
/sys/class/gpio/gpiochip510/ngpio:2



2022年5月7日 星期六

Linux Kernel(2.1)- MAJRO NUMBER RESERVED FOR DYNAMIC ASSIGNMENT


Documentation/admin-guide/devices.txt 文檔中描述了各個major number的用途, 而這文章的重點是dynamic的範圍從234~254與384~511
 234-254 char	RESERVED FOR DYNAMIC ASSIGNMENT
		Character devices that request a dynamic allocation of major number will
		take numbers starting from 254 and downward.

 384-511 char	RESERVED FOR DYNAMIC ASSIGNMENT
		Character devices that request a dynamic allocation of major
		number will take numbers starting from 511 and downward,
		once the 234-254 range is full.

相關的代碼如下:
__register_chrdev_region()
  |-> find_dynamic_major()
    |-> 254 ~ 234 or 511 ~ 384 有空的就拿來用

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
                        const char *name)
{
  struct char_device_struct *cd;
  cd = __register_chrdev_region(0, baseminor, count, name);
  if (IS_ERR(cd))
    return PTR_ERR(cd);
  *dev = MKDEV(cd->major, cd->baseminor);
  return 0;
}

static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
                           int minorct, const char *name)
{
  struct char_device_struct *cd, *curr, *prev = NULL;
  int ret;
  int i;

  if (major >= CHRDEV_MAJOR_MAX) {
    pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n",
        name, major, CHRDEV_MAJOR_MAX-1);
    return ERR_PTR(-EINVAL);
  }

  if (minorct > MINORMASK + 1 - baseminor) {
    pr_err("CHRDEV \"%s\" minor range requested (%u-%u) is out of range of maximum range (%u-%u) for a single major\n",
      name, baseminor, baseminor + minorct - 1, 0, MINORMASK);
    return ERR_PTR(-EINVAL);
  }

    cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
    if (cd == NULL)
        return ERR_PTR(-ENOMEM);

    mutex_lock(&chrdevs_lock);

    if (major == 0) {
        ret = find_dynamic_major();
        if (ret < 0) {
            pr_err("CHRDEV \"%s\" dynamic allocation region is full\n",
                 name);
            goto out;
        }
        major = ret;
    }

    ret = -EBUSY;
    i = major_to_index(major);
    for (curr = chrdevs[i]; curr; prev = curr, curr = curr->next) {
        if (curr->major < major)
            continue;

        if (curr->major > major)
            break;

        if (curr->baseminor + curr->minorct <= baseminor)
            continue;

        if (curr->baseminor >= baseminor + minorct)
            break;

        goto out;
    }

    cd->major = major;
    cd->baseminor = baseminor;
    cd->minorct = minorct;
    strlcpy(cd->name, name, sizeof(cd->name));

    if (!prev) {
        cd->next = curr;
        chrdevs[i] = cd;
    } else {
        cd->next = prev->next;
        prev->next = cd;
    }

    mutex_unlock(&chrdevs_lock);
    return cd;
out:
    mutex_unlock(&chrdevs_lock);
    kfree(cd);
    return ERR_PTR(ret);
}

/* fs/char_dev.c */
#define CHRDEV_MAJOR_MAX 512
/* Marks the bottom of the first segment of free char majors */
#define CHRDEV_MAJOR_DYN_END 234
/* Marks the top and bottom of the second segment of free char majors */
#define CHRDEV_MAJOR_DYN_EXT_START 511
#define CHRDEV_MAJOR_DYN_EXT_END 384

#define CHRDEV_MAJOR_HASH_SIZE 255
static struct char_device_struct {
    struct char_device_struct *next;
    unsigned int major;
    unsigned int baseminor;
    int minorct;
    char name[64];
    struct cdev *cdev;        /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];


static int find_dynamic_major(void)
{
    int i;
    struct char_device_struct *cd;

              /* from 254 ~ 234 */
    for (i = ARRAY_SIZE(chrdevs)-1; i >= CHRDEV_MAJOR_DYN_END; i--) {
        if (chrdevs[i] == NULL)
            return i;
    }

                /* from 511 ~ 384 */
    for (i = CHRDEV_MAJOR_DYN_EXT_START;
       i >= CHRDEV_MAJOR_DYN_EXT_END; i--) {
        for (cd = chrdevs[major_to_index(i)]; cd; cd = cd->next)
            if (cd->major == i)
                break;

        if (cd == NULL)
            return i;
    }

    return -EBUSY;
}


2021年1月10日 星期日

Linux Kernel(21)- Pulse-Width Modulation


Duty cycle,所謂的Duty Cycle指的是一段時間內on的百分比(The term duty cycle describes the proportion of 'on' time to the regular interval or 'period' of time),如下圖


Polarity,這裡的Polarity指的是high active或是low active,
 * @PWM_POLARITY_NORMAL: a high signal for the duration of the duty-
 * cycle, followed by a low signal for the remainder of the pulse
 * period
 * @PWM_POLARITY_INVERSED: a low signal for the duration of the duty-
 * cycle, followed by a high signal for the remainder of the pulse
 * period

PWM常被當成是信號調變(modulation)的一種形式,在一端進行編碼並在另一端進行解碼。通常我們會拿來控制LED或馬達等裝置。
          _      _      _      _      _      _      _      _     
         | |    | |    | |    | |    | |    | |    | |    | |    
Clock    | |    | |    | |    | |    | |    | |    | |    | |    
       __| |____| |____| |____| |____| |____| |____| |____| |____

                 _      __     ____          ____   _
PWM signal      | |    |  |   |    |        |    | | |
                | |    |  |   |    |        |    | | |
       _________| |____|  |___|    |________|    |_| |___________

Data       0     1       2      4      0      4     1      0
The inclusion of a clock signal is not necessary, as the leading edge of the data signal can be used as the clock if a small offset is added to each data value in order to avoid a data value with a zero length pulse.

                _      __     ___    _____   _      _____   __     _   
               | |    |  |   |   |  |     | | |    |     | |  |   | | 
PWM signal     | |    |  |   |   |  |     | | |    |     | |  |   | |  
             __| |____|  |___|   |__|     |_| |____|     |_|  |___| |_____

Data            0       1      2       4     0        4      1     0





2020年11月14日 星期六

Linux Kernel(18.1)- My First Filesystem


這一個章節要跟大家介紹如何寫一個filesystem,主要是讓大家對VFS的framework有一些基本認識。
首先要透過register_filesystem()註冊對應的filesystem,而register_filesystem()需要struct file_system_type的一些基本參數如下:
static struct file_system_type bv_fs_type = {
    // for internal VFS use: you should initialize this to THIS_MODULE in most cases
    .owner = THIS_MODULE,
    // the name of the filesystem type, such as "ext2", "iso9660"
    .name = "bvfs",
    // the method to call when a new instance of this filesystem should be mounted
    .mount = bvfs_mount,
    // the method to call when an instance of this filesystem should be shut down
    .kill_sb = bvfs_kill_sb,
};

MODULE_ALIAS_FS("bv");

static int __init init_bv_fs(void)
{
    printk("%s(#%d)\n", __func__, __LINE__);
    return register_filesystem(&bv_fs_type);
}
module_init(init_bv_fs);
name是filesystem type的name,mount是mount的時候會被呼叫的method,mount() method必須回傳root dentry,kill_sb則是umount會被呼叫的method。
通常mount() method會調用generic mount() implementations,包含mount_bdev、mount_nodev、mount_single並帶入fill_super() callback用於初始化struct super_block *與創建root dentry。關係圖概略如下:

在bvfs_fill_super()中主要要填入super block operations,並透過bvfs_get_inode()取得mount point/root的inode,再透過d_make_root()取得root dentry。
struct inode *bvfs_get_inode(struct super_block *sb, const struct inode *dir, umode_t mode, dev_t dev)
{
    // Allocates a new inode for given superblock.
    // The default gfp_mask for allocations related
    // to inode->i_mapping is GFP_HIGHUSER_MOVABLE.
    // If HIGHMEM pages are unsuitable or it is known
    // that pages allocated for the page cache are
    // not reclaimable or migratable, mapping_set_gfp_mask
    // must be called with suitable flags on
    // the newly created inode's mapping
    struct inode *inode = new_inode(sb);
    pr_debug("%s(#%d): mode:0%o\n", __func__, __LINE__, mode);
    if (inode) {
        inode->i_ino = get_next_ino();
        pr_debug("%s(#%d): i_ino:%lx\n", __func__, __LINE__, inode->i_ino);

        // Init uid,gid,mode for new inode according to posix standards
        inode_init_owner(inode, dir, mode);

        // add file operation
        inode->i_mapping->a_ops = &bvfs_aops;
        inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);

        switch (mode & S_IFMT) {
            default:
                init_special_inode(inode, mode, dev);
                break;

            case S_IFREG:
                inode->i_op = &bvfs_file_inode_operations;
                inode->i_fop = &bvfs_file_operations;
                break;

            case S_IFDIR:
                inode->i_op = &bvfs_dir_inode_ops;
                inode->i_fop = &simple_dir_operations;

                /* directory inodes start off with i_nlink == 2 (for "." entry) */
                inc_nlink(inode);
                break;
        }
    }
    return inode;
}

#define BVFS_MAGIC 0x20201204

/**
 * create and populate the root directory for our new filesystem.
 */
static int bvfs_fill_super(struct super_block *sb, void *data, int silent)
{
    struct bvfs_fs_info *fsi;
    struct inode *inode;

    pr_debug("%s(#%d): data:%s, silent:%d\n", __func__, __LINE__, (char *) data, silent);

    fsi = kzalloc(sizeof(*fsi), GFP_KERNEL);
    sb->s_fs_info = fsi;
    if (!fsi) {
        pr_err("%s(#%d): kzalloc()\n", __func__, __LINE__);
        return -ENOMEM;
    }
    // set default mount permission to 755
    fsi->mount_opts.mode = S_IRWXU | (S_IRGRP | S_IXGRP) | (S_IROTH | S_IXOTH);

    // The super block operations are set at the time of mounting.
    sb->s_maxbytes = MAX_LFS_FILESIZE;
    // The maximum file system block size is limited by the page cache size
    // (which is 4k on x86 systems).
    sb->s_blocksize = PAGE_SIZE;
    // The number of bits that make up the filesystem block size
    sb->s_blocksize_bits = PAGE_SHIFT;
    sb->s_magic = BVFS_MAGIC;
    sb->s_op = &bvfs_ops;
    sb->s_time_gran = 1;

    // passed S_IFDIR, the returned inode will describe a directory
    inode = bvfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
    if (!inode) {
        pr_err("%s(#%d): bvfs_get_inode failed\n", __func__, __LINE__);
        return -ENOMEM;
    }
    // This directory inode must be put into the directory cache
    // (by way of a "dentry" structure) so that the VFS can find it
    sb->s_root = d_make_root(inode);
    if (!sb->s_root) {
        pr_err("%s(#%d): d_make_root()\n", __func__, __LINE__);
        return -ENOMEM;
    }

    dump_stack();
    return 0;
}

/*
 *  @param fs_type describes the filesystem, partly initialized by the specific filesystem code
 *  @param flags mount flags
 *  @param dev_name the device name we are mounting
 *  @param data arbitrary mount options, usually comes as an ASCII string
 *
 *  @return struct dentry
 *
 */
static struct dentry *bvfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data)
{
    pr_debug("%s(#%d): flags:%d, dev_name:%s, data:%s\n", __func__, __LINE__, flags, dev_name, (char *) data);
    /*
     * Usually, a filesystem uses one of the generic mount() implementations
     * and provides a fill_super() callback instead. The generic variants are:
     *
     * mount_bdev: mount a filesystem residing on a block device
     *
     * mount_nodev: mount a filesystem that is not backed by a device
     *
     * mount_single: mount a filesystem which shares the instance between all mounts
     */
    return mount_nodev(fs_type, flags, data, bvfs_fill_super);
}

static void bvfs_kill_sb(struct super_block *sb)
{
    pr_debug("%s(#%d)\n", __func__, __LINE__);
    // kill_litter_super() is a generic function provided by the VFS;
    // it simply cleans up all of the in-core structures when the filesystem is unmounted
    kill_litter_super(sb);
}

透過簡單的mount可以更了解一下整個call flow
/ # sysctl kernel.printk='8 8 8 8'
kernel.printk = 8 8 8 8
/ # insmod bvfs.ko
[  181.936205] init_bv_fs(#425)

/ # mount bvfs mnt -t bvfs
sys_mount()
  |-> ksys_mount()
    |-> do_mount()
      |-> do_new_mount()
        |-> vfs_kern_mount()
          |-> mount_fs()
            |-> bvfs_mount()
              |-> mount_nodev()
                |-> bvfs_fill_super()
bvfs_mount(#392): flags:32768, dev_name:bvfs, data:(null)
bvfs_fill_super(#346): data:(null), silent:1
bvfs_get_inode(#302): mode:040755
bvfs_get_inode(#305): i_ino:1340
CPU: 0 PID: 810 Comm: mount Tainted: G           O      4.19.0-rc8+ #32
Hardware name: ARM-Versatile Express
[<80111eb4>] (unwind_backtrace) from [<8010d8e4>] (show_stack+0x10/0x14)
[<8010d8e4>] (show_stack) from [<806c81b0>] (dump_stack+0x88/0x9c)
[<806c81b0>] (dump_stack) from [<7f000b34>] (bvfs_fill_super+0x108/0x124 [bvfs])
[<7f000b34>] (bvfs_fill_super [bvfs]) from [<8024d8ec>] (mount_nodev+0x44/0x90)
[<8024d8ec>] (mount_nodev) from [<7f000468>] (bvfs_mount+0x68/0x78 [bvfs])
[<7f000468>] (bvfs_mount [bvfs]) from [<8024e3f0>] (mount_fs+0x14/0xa8)
[<8024e3f0>] (mount_fs) from [<8026af6c>] (vfs_kern_mount.part.3+0x48/0xf8)
[<8026af6c>] (vfs_kern_mount.part.3) from [<8026d80c>] (do_mount+0x57c/0xc80)
[<8026d80c>] (do_mount) from [<8026e2a4>] (ksys_mount+0x8c/0xb4)
[<8026e2a4>] (ksys_mount) from [<80101000>] (ret_fast_syscall+0x0/0x54)
Exception stack(0x8550dfa8 to 0x8550dff0)
dfa0:                   00000000 00000000 7e9d8f85 7e9d8f8a 7e9d8f91 00008000
dfc0: 00000000 00000000 7e9d8f85 00000015 7e9d8f91 00008000 00000000 00000000
dfe0: 00000000 7e9d8b68 0007e5a7 00012fba


/ # cd mnt
sys_stat64()
  |-> vfs_statx()
    |-> filename_lookup()
      |-> walk_component()
        |-> lookup_slow()
          |-> __lookup_slow()
            |-> bvfs_lookup()

bvfs_lookup(#218):
CPU: 0 PID: 800 Comm: sh Tainted: G           O      4.19.0-rc8+ #32
Hardware name: ARM-Versatile Express
[<80111eb4>] (unwind_backtrace) from [<8010d8e4>] (show_stack+0x10/0x14)
[<8010d8e4>] (show_stack) from [<806c81b0>] (dump_stack+0x88/0x9c)
[<806c81b0>] (dump_stack) from [<7f000224>] (bvfs_lookup+0x40/0x5c [bvfs])
[<7f000224>] (bvfs_lookup [bvfs]) from [<802551a0>] (__lookup_slow+0x8c/0x154)
[<802551a0>] (__lookup_slow) from [<80255298>] (lookup_slow+0x30/0x44)
[<80255298>] (lookup_slow) from [<802579c8>] (walk_component+0x1c8/0x300)
[<802579c8>] (walk_component) from [<802580c8>] (path_lookupat+0x70/0x1fc)
[<802580c8>] (path_lookupat) from [<80259da8>] (filename_lookup+0x9c/0x10c)
[<80259da8>] (filename_lookup) from [<8024f3d0>] (vfs_statx+0x68/0xd0)
[<8024f3d0>] (vfs_statx) from [<8024fbc8>] (sys_stat64+0x38/0x68)
[<8024fbc8>] (sys_stat64) from [<80101000>] (ret_fast_syscall+0x0/0x54)
Exception stack(0x85505fa8 to 0x85505ff0)
5fa0:                   0018a058 00000000 0018a25c 7e97caf8 7e97caf8 0018a266
5fc0: 0018a058 00000000 00000001 000000c3 001864dc 00186478 00000000 7e97cadc
5fe0: 000000c3 7e97cac4 000ddb9b 00011726


基本上dentry就是一個representation/cache結構,用於反應file-system的目錄結構,而不須真的讀從file-system讀出資料。
A "dentry" in the Linux kernel is the in-memory representation of a directory entry;
it is a way of remembering the resolution of a given file or directory name 
without having to search through the filesystem to find it. 
The dentry cache speeds lookups considerably; 
keeping dentries for frequently accessed names like /tmp, /dev/null, 
or /usr/bin/tetris saves a lot of filesystem I/O.


概略的一些關係圖如下,希望能讓大家能對這些關係有較清楚的概念。



目前為止的功能只能達到mount,其餘的下一個章節繼續囉。


2020年10月9日 星期五

How MSM Parse Partition Table in Kernel


call flow
msm_nand_probe()
  |-> msm_nand_parse_smem_ptable() 
    |-> smem_get_entry(SMEM_AARM_PARTITION_TABLE)
  |-> mtd_device_parse_register()


code snippet, https://android.googlesource.com/kernel/msm/+/android-msm-hammerhead-3.4-kk-r1/drivers/mtd/devices/msm_qpic_nand.c
#define FLASH_PART_MAGIC1        0x55EE73AA
#define FLASH_PART_MAGIC2        0xE35EBDDB
#define FLASH_PTABLE_V3         3
#define FLASH_PTABLE_V4         4
#define FLASH_PTABLE_MAX_PARTS_V3  16
#define FLASH_PTABLE_MAX_PARTS_V4  32
#define FLASH_PTABLE_HDR_LEN     (4*sizeof(uint32_t))

// Parititon Table Store in Flash.
struct flash_partition_entry {
  char name[FLASH_PTABLE_ENTRY_NAME_SIZE];
  u32 offset;   /* Offset in blocks from beginning of device */
  u32 length;   /* Length of the partition in blocks */
  u8 attr;     /* Flags for this partition */
};

struct flash_partition_table {
  u32 magic1;
  u32 magic2;
  u32 version;
  u32 numparts;
  struct flash_partition_entry part_entry[FLASH_PTABLE_MAX_PARTS_V4];
};

static struct mtd_partition mtd_part[FLASH_PTABLE_MAX_PARTS_V4];

static int __devinit msm_nand_probe(struct platform_device *pdev)
{
  ...
  err = msm_nand_parse_smem_ptable(&nr_parts);
  ...
  for (i = 0; i < nr_parts; i++) {
    mtd_part[i].offset *= info->mtd.erasesize;
    mtd_part[i].size *= info->mtd.erasesize;
  }
  err = mtd_device_parse_register(&info->mtd, NULL, NULL, &mtd_part[0], nr_parts);
  ...
}

// Get Partition Table from RAM/Flash via smem_get_entry(SMEM_AARM_PARTITION_TABLE).
static int msm_nand_parse_smem_ptable(int *nr_parts)
{
  uint32_t  i, j;
  uint32_t len = FLASH_PTABLE_HDR_LEN;
  struct flash_partition_entry *pentry;
  char *delimiter = ":";
  pr_info("Parsing partition table info from SMEM\n");
  /* Read only the header portion of ptable */
  ptable = *(struct flash_partition_table *)
            (smem_get_entry(SMEM_AARM_PARTITION_TABLE, &len));
  /* Verify ptable magic */
  if (ptable.magic1 != FLASH_PART_MAGIC1 || 
      ptable.magic2 != FLASH_PART_MAGIC2) {
    pr_err("Partition table magic verification failed\n");
    goto out;
  }
  /* Ensure that # of partitions is less than the max we have allocated */
  if (ptable.numparts > FLASH_PTABLE_MAX_PARTS_V4) {
    pr_err("Partition numbers exceed the max limit\n");
    goto out;
  }
  /* Find out length of partition data based on table version. */
  if (ptable.version <= FLASH_PTABLE_V3) {
    len = FLASH_PTABLE_HDR_LEN + FLASH_PTABLE_MAX_PARTS_V3 *
        sizeof(struct flash_partition_entry);
  } else if (ptable.version == FLASH_PTABLE_V4) {
    len = FLASH_PTABLE_HDR_LEN + FLASH_PTABLE_MAX_PARTS_V4 *
        sizeof(struct flash_partition_entry);
  } else {
    pr_err("Unknown ptable version (%d)", ptable.version);
    goto out;
  }
  *nr_parts = ptable.numparts;
  ptable = *(struct flash_partition_table *)
        (smem_get_entry(SMEM_AARM_PARTITION_TABLE, &len));
  for (i = 0; i < ptable.numparts; i++) {
    pentry = &ptable.part_entry[i];
    if (pentry->name == '\0')
      continue;
    /* Convert name to lower case and discard the initial chars */
    mtd_part[i].name = pentry->name;
    for (j = 0; j < strlen(mtd_part[i].name); j++)
      *(mtd_part[i].name + j) = tolower(*(mtd_part[i].name + j));
      strsep(&(mtd_part[i].name), delimiter);
      mtd_part[i].offset      = pentry->offset;
      mtd_part[i].mask_flags  = pentry->attr;
      mtd_part[i].size        = pentry->length;
      pr_debug("%d: %s offs=0x%08x size=0x%08x attr:0x%08x\n",
          i, pentry->name, pentry->offset, pentry->length, pentry->attr);
  }
  pr_info("SMEM partition table found: ver: %d len: %d\n",
      ptable.version, ptable.numparts);
  return 0;
out:
  return -EINVAL;
}

static const struct of_device_id msm_nand_match_table[] = {
  { .compatible = "qcom,msm-nand", },
  {},
};


2020年9月27日 星期日

Linux Kernel(10.3.1)- Command line partition table parsing for Kernel 4.19


由於10.3的kernel過時,所以10.3.1是用kernel 4.19來做補充。
MTD Partition除了在code中寫死以外,其實還可以透過一些parsers來作規劃,這一章就要來教大家如何使用"Command line partition table parsing"。
首先必須在kernel中啟用"Command line partition table parsing",請參照10.3。
接著在command line中加入mtdparts的設定,其簡單說明如下:
 * mtdparts=<mtddef>[;<mtddef]
 * <mtddef>  := <mtd-id>:<partdef>[,<partdef>]
 * <partdef> := <size>[@<offset>][<name>][ro][lk]
 * <mtd-id>  := unique name used in mapping driver/device (mtd->name)
 * <size>    := standard linux memsize OR "-" to denote all remaining space
 *              size is automatically truncated at end of device
 *              if specified or truncated size is 0 the part is skipped
 * <offset>  := standard linux memsize
 *              if omitted the part will immediately follow the previous part
 *              or 0 if the first part
 * <name>    := '(' NAME ')'
 *              NAME will appear in /proc/mtd
 *
 * <size> and <offset> can be specified such that the parts are out of order
 * in physical memory and may even overlap.
 *
 * The parts are assigned MTD numbers in the order they are specified in the
 * command line regardless of their order in physical memory.

 * Due to the way Linux handles the command line, no spaces are
 * allowed in the partition definition, including mtd id's and partition
 * names.

這裡我用qemu與mtdram來執行,執行參數如下
qemu-system-arm -s -M vexpress-a9 -m 128M -kernel ./linux/arch/arm/boot/zImage \
                -dtb ./linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -initrd ./initrd-arm.img \
                -nographic -append "console=ttyAMA0 mtdparts=\"mtdram test device:1024k(p1),1024k(p2),-(p3)\""

這裡特別要說明的是mtd-id就是mtd name,基本上你可以以cat /proc/mtd,就可以知道mtd name了,或者trace一下code也行
// file "drivers/mtd/cmdlinepart.c"
static int parse_cmdline_partitions(struct mtd_info *master,
                    const struct mtd_partition **pparts,
                    struct mtd_part_parser_data *data)
{
    ...
const char *mtd_id = master->name;
    /*
     * Search for the partition definition matching master->name.
     * If master->name is not set, stop at first partition definition.
     */
    for (part = partitions; part; part = part->next) {
        if ((!mtd_id) || (!strcmp(part->mtd_id, mtd_id)))
            break;
    }
    ...
}

// drivers/mtd/devices/mtdram.c
static int __init init_mtdram(void)
{
    ...
    err = mtdram_init_device(mtd_info, addr, MTDRAM_TOTAL_SIZE, "mtdram test device");
    ...
}

int mtdram_init_device(struct mtd_info *mtd, void *mapped_address,
        unsigned long size, const char *name)
{
    ...
    /* Setup the MTD structure */
    mtd->name = name;
    ...
}

此外我多印了一些deubg資訊在drivers/mtd/cmdlinepart.c
diff --git a/drivers/mtd/cmdlinepart.c b/drivers/mtd/cmdlinepart.c
index 3ea44cff9b75..a411ce85097e 100644
--- a/drivers/mtd/cmdlinepart.c
+++ b/drivers/mtd/cmdlinepart.c
@@ -48,6 +48,7 @@
  * edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home)
  */

+#define DEBGU
 #define pr_fmt(fmt)    "mtd: " fmt

 #include 
@@ -58,7 +59,7 @@
 #include 

 /* debug macro */
-#if 0
+#if 1
 #define dbg(x) do { printk("DEBUG-CMDLINE-PART: "); printk x; } while(0)
 #else
 #define dbg(x)
@@ -315,6 +316,7 @@ static int parse_cmdline_partitions(struct mtd_info *master,
        struct cmdline_mtd_partition *part;
        const char *mtd_id = master->name;

+       printk("BROOK, %s(#%d), cmdline_parsed:%d\n", __func__, __LINE__, cmdline_parsed);
        /* parse command line */
        if (!cmdline_parsed) {
                err = mtdpart_setup_real(cmdline);
@@ -327,12 +329,15 @@ static int parse_cmdline_partitions(struct mtd_info *master,
         * If master->name is not set, stop at first partition definition.
         */
        for (part = partitions; part; part = part->next) {
+           printk("BROOK, %s(#%d), mtd_id:%s, part->mtd_id:%s\n", __func__, __LINE__, mtd_id, part->mtd_id);
                if ((!mtd_id) || (!strcmp(part->mtd_id, mtd_id)))
                        break;
        }

-       if (!part)
+       printk("BROOK, %s(#%d), part:%p\n", __func__, __LINE__, part);
+       if (!part) {
                return 0;
+       }

        for (i = 0, offset = 0; i < part->num_parts; i++) {
                if (part->parts[i].offset == OFFSET_CONTINUOUS)

執行結果如下:
/ # dmesg
...
[    4.688086] BROOK, parse_cmdline_partitions(#319), cmdline_parsed:0
[    4.688876] DEBUG-CMDLINE-PART:
[    4.688994] parsing <1024k(p1),1024k(p2),-(p3)>
[    4.690787] DEBUG-CMDLINE-PART:
[    4.690944] partition 2: name <p3>, offset ffffffffffffffff, size ffffffffffffffff, mask flags 0
[    4.692017] DEBUG-CMDLINE-PART:
[    4.692102] partition 1: name <p2>, offset ffffffffffffffff, size 100000, mask flags 0
[    4.692950] DEBUG-CMDLINE-PART:
[    4.693034] partition 0: name <p1>, offset ffffffffffffffff, size 100000, mask flags 0
[    4.693993] DEBUG-CMDLINE-PART:
[    4.694101] mtdid=<mtdram test device> num_parts=<3>
[    4.695000] BROOK, parse_cmdline_partitions(#332), mtd_id:40000000.flash, part->mtd_id:mtdram test device
[    4.696546] BROOK, parse_cmdline_partitions(#337), part:  (null)
[    4.763129] BROOK, parse_cmdline_partitions(#319), cmdline_parsed:1
[    4.763742] BROOK, parse_cmdline_partitions(#332), mtd_id:48000000.psram, part->mtd_id:mtdram test device
[    4.764461] BROOK, parse_cmdline_partitions(#337), part:  (null)
[    4.806551] BROOK, parse_cmdline_partitions(#319), cmdline_parsed:1
[    4.808150] BROOK, parse_cmdline_partitions(#332), mtd_id:mtdram test device, part->mtd_id:mtdram test device
[    4.809227] BROOK, parse_cmdline_partitions(#337), part:(ptrval)
[    4.810325] 3 cmdlinepart partitions found on MTD device mtdram test device
[    4.811006] Creating 3 MTD partitions on "mtdram test device":
[    4.813103] 0x000000000000-0x000000100000 : "p1"
[    4.830841] 0x000000100000-0x000000200000 : "p2"
[    4.846502] 0x000000200000-0x000000400000 : "p3"
...

/ # cat /proc/mtd
dev:    size   erasesize  name
mtd0: 08000000 00040000 "40000000.flash"
mtd1: 02000000 00001000 "48000000.psram"
mtd2: 00100000 00020000 "p1"
mtd3: 00100000 00020000 "p2"
mtd4: 00200000 00020000 "p3"

/ # zcat /proc/config.gz | grep -i MTDRAM
CONFIG_MTD_MTDRAM=y
CONFIG_MTDRAM_TOTAL_SIZE=4096
CONFIG_MTDRAM_ERASE_SIZE=128




熱門文章