I decided to jump on the Linux-capable RISC-V bandwagon by buying myself the AllWinner Nezha D1. Ever since it arrived, I've spent the last couple evenings working with it to gauge its suitability for inclusion in some projects. Overall my impression has been quite positive!
I won't bore you here with specs you can easily find elsewhere, but the gist is the Nezha-D1 is a 64-bit, hard-core, RISC-V based board in a roughly RaspberryPi form-factor, with a RaspberryPi-style 40-pin expansion header.
Yocto
My first question with a new board is always: "can I build an image for it with Yocto?" If I can't use Yocto with it, then I'm not interested. In fact, I always do a test build in Yocto before even thinking of buying a board. Of course I can't know if the image will work until I actually get the board, but if that basic step doesn't work, then the board is not worth my time or money. (Unless, of course, I'm somehow thinking that I might take a crack at adding Yocto support for the board)
To see if Yocto support is even a possibility, check: https://layers.openembedded.org/ . Verify the branch is "master", click on "Machines" and type "nezha" into the search bar: https://layers.openembedded.org/layerindex/branch/master/machines/?q=nezha&search=1 There is a "nezha-allwinner-d1" machine defined in the meta-riscv layer.
The first thing I do is to download meta-riscv, its dependencies (openembedded-core), as well as bitbake, then perform a nodistro core-image-base build to see if it succeeds. It does, so I proceed to buy the board.
Build
Here are the details of my build.
conf/local.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | MACHINE = "nezha-allwinner-d1" DISTRO = "nodistro" DL_DIR = "/opt/Downloads" DISTRO_FEATURES = "" INHERT += "buildhistory image-buildinfo buildstats-summary" BUILDHISTORY_COMMIT = "1" SSTATE_DIR = "/z/sstate" PACKAGE_CLASSES ?= "package_ipk" EXTRA_IMAGE_FEATURES ?= "debug-tweaks" USER_CLASSES ?= "buildstats" PATCHRESOLVE = "noop" BB_DISKMON_DIRS ??= "\ STOPTASKS,${TMPDIR},1G,100K \ STOPTASKS,${DL_DIR},1G,100K \ STOPTASKS,${SSTATE_DIR},1G,100K \ STOPTASKS,/tmp,100M,100K \ HALT,${TMPDIR},100M,1K \ HALT,${DL_DIR},100M,1K \ HALT,${SSTATE_DIR},100M,1K \ HALT,/tmp,10M,1K" PACKAGECONFIG:append:pn-qemu-system-native = " sdl" PACKAGECONFIG:append:pn-nativesdk-qemu = " sdl" CONF_VERSION = "2" |
conf/bblayers.conf
1 2 3 4 5 6 7 8 9 10 11 | # LAYER_CONF_VERSION is increased each time build/conf/bblayers.conf # changes incompatibly LCONF_VERSION = "7" BBPATH = "${TOPDIR}" BBFILES ?= "" BBLAYERS ?= " \ /z/build-master/nezha/layers/meta-riscv \ /z/build-master/nezha/layers/openembedded-core/meta \ " |
build
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | $ bitbake core-image-base Loading cache: 100% | | ETA: --:--:-- Loaded 0 entries from dependency cache. Parsing recipes: 100% |################################################################| Time: 0:00:06 Parsing of 907 .bb files complete (0 cached, 907 parsed). 1710 targets, 224 skipped, 0 masked, 0 errors. NOTE: Resolving any missing task queue dependencies Build Configuration: BB_VERSION = "2.2.0" BUILD_SYS = "x86_64-linux" NATIVELSBSTRING = "opensuseleap-15.3" TARGET_SYS = "riscv64-oe-linux" MACHINE = "nezha-allwinner-d1" DISTRO = "nodistro" DISTRO_VERSION = "nodistro.0" TUNE_FEATURES = "riscv64" meta-riscv = "master:d6e3efd54a3c1361fecf2a56c6f4f590fbe676d9" meta = "master:47f6a75960b3af2be7f45fd06e2fb73549b6933b" Initialising tasks: 100% |#############################################################| Time: 0:00:01 Sstate summary: Wanted 765 Local 3 Mirrors 0 Missed 762 Current 0 (0% match, 0% complete) NOTE: Executing Tasks WARNING: opensbi-1.1-r0 do_package_qa: QA Issue: File /share/opensbi/lp64/generic/firmware/fw_jump.elf in package opensbi contains reference to TMPDIR File /share/opensbi/lp64/generic/firmware/fw_dynamic.elf in package opensbi contains reference to TMPDIR File /share/opensbi/lp64/generic/firmware/fw_payload.elf in package opensbi contains reference to TMPDIR [buildpaths] WARNING: opensbi-1.1-r0 do_package_qa: QA Issue: File /share/opensbi/lp64/generic/firmware/.debug/fw_jump.elf in package opensbi-dbg contains reference to TMPDIR File /share/opensbi/lp64/generic/firmware/.debug/fw_dynamic.elf in package opensbi-dbg contains reference to TMPDIR File /share/opensbi/lp64/generic/firmware/.debug/fw_payload.elf in package opensbi-dbg contains reference to TMPDIR [buildpaths] NOTE: Tasks Summary: Attempted 2031 tasks of which 3 didn't need to be rerun and all succeeded. Summary: There were 2 WARNING messages. |
First Boot
After the build completes successfully, flash the image to an SDcard. Most BSP layers create an <image>-<machine>.wic file (sometimes it's just wic by itself, sometimes it's compressed in one way or another to produce *wic.gz, *wic.bz2, and/or *wic.xz). One of these wic files needs to be flashed with bmaptool. You could download, build, and install bmaptool yourself, or you could get Yocto to do it for you:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | $ bitbake bmap-tools-native -caddto_recipe_sysroot Loading cache: 100% |##################################################################| Time: 0:00:00 Loaded 1709 entries from dependency cache. Parsing recipes: 100% |################################################################| Time: 0:00:00 Parsing of 907 .bb files complete (906 cached, 1 parsed). 1710 targets, 224 skipped, 0 masked, 0 errors. NOTE: Resolving any missing task queue dependencies Build Configuration: BB_VERSION = "2.2.0" BUILD_SYS = "x86_64-linux" NATIVELSBSTRING = "opensuseleap-15.3" TARGET_SYS = "riscv64-oe-linux" MACHINE = "nezha-allwinner-d1" DISTRO = "nodistro" DISTRO_VERSION = "nodistro.0" TUNE_FEATURES = "riscv64" meta-riscv = "master:d6e3efd54a3c1361fecf2a56c6f4f590fbe676d9" meta = "master:47f6a75960b3af2be7f45fd06e2fb73549b6933b" Initialising tasks: 100% |#############################################################| Time: 0:00:01 Sstate summary: Wanted 0 Local 0 Mirrors 0 Missed 0 Current 76 (0% match, 100% complete) NOTE: Executing Tasks NOTE: Tasks Summary: Attempted 343 tasks of which 342 didn't need to be rerun and all succeeded. |
Now to invoke your Yocto-built bmaptool use:
1 2 3 4 5 6 7 8 9 10 | $ oe-run-native bmap-tools-native bmaptool copy tmp-glibc/deploy/images/nezha-allwinner-d1/core-image-base-nezha-allwinner-d1.wic.gz /dev/sdl Running bitbake -e bmap-tools-native bmaptool: info: discovered bmap file 'tmp-glibc/deploy/images/nezha-allwinner-d1/core-image-base-nezha-allwinner-d1.wic.bmap' bmaptool: info: block map format version 2.0 bmaptool: info: 3601408 blocks of size 4096 (13.7 GiB), mapped 21501 blocks (84.0 MiB or 0.6%) bmaptool: info: copying image 'core-image-base-nezha-allwinner-d1.wic.gz' to block device '/dev/sdl' using bmap file 'core-image-base-nezha-allwinner-d1.wic.bmap' bmaptool: WARNING: failed to disable excessive buffering, expect worse system responsiveness (reason: cannot set max. I/O ratio to 1: [Errno 13] Permission denied: '/sys/dev/block/8:176/bdi/max_ratio') bmaptool: info: 100% copied bmaptool: info: synchronizing '/dev/sdl' bmaptool: info: copying time: 38.4s, copying speed 2.2 MiB/sec |
(make sure to replace /dev/sdl with which ever device your SDcard is using on your specific host machine)
Plugging your newly-flashed SDcard into your Nezha board, connecting a serial cable, opening up a console application on your host, and applying power to the board you'll see:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 | U-Boot SPL 2022.10 (Nov 01 2022 - 04:27:57 +0000) sunxi_ram_probe: dram-controller@3102000: probing DRAM only have internal ZQ!! ddr_efuse_type: 0x0 [AUTO DEBUG] two rank and full DQ! ddr_efuse_type: 0x0 [AUTO DEBUG] rank 0 row = 15 [AUTO DEBUG] rank 0 bank = 8 [AUTO DEBUG] rank 0 page size = 2 KB [AUTO DEBUG] rank 1 row = 15 [AUTO DEBUG] rank 1 bank = 8 [AUTO DEBUG] rank 1 page size = 2 KB rank1 config same as rank0 DRAM BOOT DRIVE INFO: V0.24 DRAM CLK = 792 MHz DRAM Type = 3 (2:DDR2,3:DDR3) DRAMC ZQ value: 0x7b7bfb DRAM ODT value: 0x42. ddr_efuse_type: 0x0 DRAM SIZE =1024 M DRAM simple test OK. mxstatus=0xc0408000 mhcr=0x00000109 mcor=0x00000003 mhint=0x00004000 Trying to boot from MMC1 PLL reg = 0xf8216300, freq = 1200000000 SPL size = 81920, sector = 160 sunxi_ram_get_info: dram-controller@3102000: getting info OpenSBI v1.1 ____ _____ ____ _____ / __ \ / ____| _ \_ _| | | | |_ __ ___ _ __ | (___ | |_) || | | | | | '_ \ / _ \ '_ \ \___ \| _ < | | | |__| | |_) | __/ | | |____) | |_) || |_ \____/| .__/ \___|_| |_|_____/|____/_____| | | |_| Platform Name : Allwinner D1 Nezha Platform Features : medeleg Platform HART Count : 1 Platform IPI Device : --- Platform Timer Device : --- @ 0Hz Platform Console Device : uart8250 Platform HSM Device : sun20i-d1-ppu Platform Reboot Device : sunxi-wdt-reset Platform Shutdown Device : --- Firmware Base : 0x80000000 Firmware Size : 276 KB Runtime SBI Version : 1.0 Domain0 Name : root Domain0 Boot HART : 0 Domain0 HARTs : 0* Domain0 Region00 : 0x0000000080000000-0x000000008007ffff () Domain0 Region01 : 0x0000000000000000-0xffffffffffffffff (R,W,X) Domain0 Next Address : 0x0000000042e00000 Domain0 Next Arg1 : 0x0000000042e8b7f8 Domain0 Next Mode : S-mode Domain0 SysReset : yes Boot HART ID : 0 Boot HART Domain : root Boot HART Priv Version : v1.11 Boot HART Base ISA : rv64imafdcvx Boot HART ISA Extensions : time Boot HART PMP Count : 8 Boot HART PMP Granularity : 2048 Boot HART PMP Address Bits: 38 Boot HART MHPM Count : 0 Boot HART MIDELEG : 0x0000000000000222 Boot HART MEDELEG : 0x000000000000b109 sunxi_set_gate: (CLK#24) unhandled U-Boot 2022.10 (Nov 01 2022 - 04:27:57 +0000) Allwinner Technology DRAM: 1 GiB sunxi_set_gate: (CLK#24) unhandled Core: 54 devices, 20 uclasses, devicetree: separate WDT: Started watchdog@6011000 with servicing every 1000ms (16s timeout) MMC: mmc@4020000: 0, mmc@4021000: 1 Loading Environment from FAT... PLL reg = 0xf8216300, freq = 1200000000 Unable to read "uboot.env" from mmc0:1... In: serial@2500000 Out: serial@2500000 Err: serial@2500000 Net: Warning: ethernet@4500000 (eth0) using random MAC address - c6:3e:31:4e:f8:ec eth0: ethernet@4500000 starting USB... Bus usb@4101000: USB EHCI 1.00 Bus usb@4101400: USB OHCI 1.0 Bus usb@4200000: USB EHCI 1.00 Bus usb@4200400: USB OHCI 1.0 scanning bus usb@4101000 for devices... 1 USB Device(s) found scanning bus usb@4101400 for devices... 1 USB Device(s) found scanning bus usb@4200000 for devices... 1 USB Device(s) found scanning bus usb@4200400 for devices... 1 USB Device(s) found scanning usb for storage devices... 0 Storage Device(s) found Hit any key to stop autoboot: 2 ^H^H^H 1 ^H^H^H 0 PLL reg = 0xf8216300, freq = 1200000000 switch to partitions #0, OK mmc0 is current device Scanning mmc 0:1... Found U-Boot script /boot.scr.uimg 1249 bytes read in 1 ms (1.2 MiB/s) ## Executing script at 41900000 ethernet@4500000 Waiting for PHY auto negotiation to complete......... TIMEOUT ! ethernet@4500000 Waiting for PHY auto negotiation to complete......... TIMEOUT ! ethernet@4500000 Waiting for PHY auto negotiation to complete......... TIMEOUT ! 322 bytes read in 1 ms (314.5 KiB/s) 5431248 bytes read in 898 ms (5.8 MiB/s) ## Loading kernel from FIT Image at 41c00000 ... Using 'conf-1' configuration Trying 'kernel-1' kernel subimage Description: Linux kernel Type: Kernel Image Compression: gzip compressed Data Start: 0x41c0010c Data Size: 5428090 Bytes = 5.2 MiB Architecture: RISC-V OS: Linux Load Address: 0x40200000 Entry Point: 0x40200000 Hash algo: sha256 Hash value: c0897b7ecc8c79a67ffd0b71af8a90e3bdeb028c9953a13a8a4b2c0dab4e3797 Verifying Hash Integrity ... sha256+ OK ## Flattened Device Tree blob at 7fd3e180 Booting using the fdt blob at 0x7fd3e180 Working FDT set to 7fd3e180 Uncompressing Kernel Image Loading Device Tree to 0000000042df2000, end 0000000042dffc27 ... OK Working FDT set to 42df2000 Starting kernel ... [ 0.000000] Linux version 6.1.0-rc3-nezha (oe-user@oe-host) (riscv64-oe-linux-gcc (GCC) 12.2.0, GNU ld (GNU Binutils) 2.39.0.20220819) #1 PREEMPT Thu Nov 3 04:49:55 UTC 2022 [ 0.000000] OF: fdt: Ignoring memory range 0x40000000 - 0x40200000 [ 0.000000] Machine model: Allwinner D1 Nezha [ 0.000000] efi: UEFI not found. [ 0.000000] Zone ranges: [ 0.000000] DMA32 [mem 0x0000000040200000-0x000000007fffffff] [ 0.000000] Normal empty [ 0.000000] Movable zone start for each node [ 0.000000] Early memory node ranges [ 0.000000] node 0: [mem 0x0000000040200000-0x000000007fffffff] [ 0.000000] Initmem setup node 0 [mem 0x0000000040200000-0x000000007fffffff] [ 0.000000] SBI specification v1.0 detected [ 0.000000] SBI implementation ID=0x1 Version=0x10001 [ 0.000000] SBI TIME extension detected [ 0.000000] SBI IPI extension detected [ 0.000000] SBI RFENCE extension detected [ 0.000000] SBI SRST extension detected [ 0.000000] riscv: base ISA extensions acdfim [ 0.000000] riscv: ELF capabilities acdfim [ 0.000000] pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768 [ 0.000000] pcpu-alloc: [0] 0 [ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 257544 [ 0.000000] Kernel command line: earlycon=sbi clk_ignore_unused initcall_debug=0 console=ttyS0,115200 loglevel=8 root=/dev/mmcblk0p2 rootwait init=/sbin/init [ 0.000000] Dentry cache hash table entries: 131072 (order: 8, 1048576 bytes, linear) [ 0.000000] Inode-cache hash table entries: 65536 (order: 7, 524288 bytes, linear) [ 0.000000] mem auto-init: stack:all(zero), heap alloc:off, heap free:off [ 0.000000] Memory: 1009936K/1046528K available (6082K kernel code, 4935K rwdata, 4096K rodata, 2127K init, 309K bss, 36592K reserved, 0K cma-reserved) [ 0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1 [ 0.000000] rcu: Preemptible hierarchical RCU implementation. [ 0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies. [ 0.000000] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0 [ 0.000000] riscv-intc: 64 local interrupts mapped [ 0.000000] plic: interrupt-controller@10000000: mapped 176 interrupts with 1 handlers for 2 contexts. [ 0.000000] rcu: srcu_init: Setting srcu_struct sizes based on contention. [ 0.000000] riscv-timer: riscv_timer_init_dt: Registering clocksource cpuid [0] hartid [0] [ 0.000000] clocksource: riscv_clocksource: mask: 0xffffffffffffffff max_cycles: 0x588fe9dc0, max_idle_ns: 440795202592 ns [ 0.000001] sched_clock: 64 bits at 24MHz, resolution 41ns, wraps every 4398046511097ns [ 0.000518] clocksource: timer: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 79635851949 ns [ 0.001450] Calibrating delay loop (skipped), value calculated using timer frequency.. 48.00 BogoMIPS (lpj=96000) [ 0.001488] pid_max: default: 32768 minimum: 301 [ 0.001857] LSM: Security Framework initializing [ 0.002127] Mount-cache hash table entries: 2048 (order: 2, 16384 bytes, linear) [ 0.002175] Mountpoint-cache hash table entries: 2048 (order: 2, 16384 bytes, linear) [ 0.006058] riscv: ELF compat mode unsupported [ 0.006135] ASID allocator using 16 bits (65536 entries) [ 0.006425] rcu: Hierarchical SRCU implementation. [ 0.006439] rcu: Max phase no-delay instances is 1000. [ 0.006686] EFI services will not be available. [ 0.007641] devtmpfs: initialized [ 0.031148] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns [ 0.031195] futex hash table entries: 256 (order: 0, 6144 bytes, linear) [ 0.031431] pinctrl core: initialized pinctrl subsystem [ 0.034816] NET: Registered PF_NETLINK/PF_ROUTE protocol family [ 0.035494] DMA: preallocated 128 KiB GFP_KERNEL pool for atomic allocations [ 0.035588] DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA32 pool for atomic allocations [ 0.036553] thermal_sys: Registered thermal governor 'bang_bang' [ 0.036664] thermal_sys: Registered thermal governor 'step_wise' [ 0.036681] thermal_sys: Registered thermal governor 'user_space' [ 0.073318] platform 5460000.tcon-top: Fixing up cyclic dependency with 5200000.mixer [ 0.073562] platform 5460000.tcon-top: Fixing up cyclic dependency with 5100000.mixer [ 0.074743] platform 5461000.lcd-controller: Fixing up cyclic dependency with 5460000.tcon-top [ 0.076089] platform 5470000.lcd-controller: Fixing up cyclic dependency with 5460000.tcon-top [ 0.077337] platform 5500000.hdmi: Fixing up cyclic dependency with 5460000.tcon-top [ 0.081586] platform 7090000.rtc: Fixing up cyclic dependency with 7010000.clock-controller [ 0.085046] platform connector: Fixing up cyclic dependency with 5500000.hdmi [ 0.180753] raid6: int64x8 gen() 156 MB/s [ 0.248633] raid6: int64x4 gen() 198 MB/s [ 0.316715] raid6: int64x2 gen() 180 MB/s [ 0.384610] raid6: int64x1 gen() 161 MB/s [ 0.384629] raid6: using algorithm int64x4 gen() 198 MB/s [ 0.452592] raid6: .... xor() 123 MB/s, rmw enabled [ 0.452607] raid6: using intx1 recovery algorithm [ 0.455199] iommu: Default domain type: Translated [ 0.455218] iommu: DMA domain TLB invalidation policy: strict mode [ 0.456083] SCSI subsystem initialized [ 0.456737] usbcore: registered new interface driver usbfs [ 0.456861] usbcore: registered new interface driver hub [ 0.456981] usbcore: registered new device driver usb [ 0.458805] Advanced Linux Sound Architecture Driver Initialized. [ 0.460733] clocksource: Switched to clocksource timer [ 0.465108] NET: Registered PF_INET protocol family [ 0.465711] IP idents hash table entries: 16384 (order: 5, 131072 bytes, linear) [ 0.473890] tcp_listen_portaddr_hash hash table entries: 512 (order: 0, 4096 bytes, linear) [ 0.473950] Table-perturb hash table entries: 65536 (order: 6, 262144 bytes, linear) [ 0.474007] TCP established hash table entries: 8192 (order: 4, 65536 bytes, linear) [ 0.474262] TCP bind hash table entries: 8192 (order: 5, 131072 bytes, linear) [ 0.474969] TCP: Hash tables configured (established 8192 bind 8192) [ 0.475212] UDP hash table entries: 512 (order: 2, 16384 bytes, linear) [ 0.475322] UDP-Lite hash table entries: 512 (order: 2, 16384 bytes, linear) [ 0.475767] NET: Registered PF_UNIX/PF_LOCAL protocol family [ 0.477573] workingset: timestamp_bits=46 max_order=18 bucket_order=0 [ 0.645753] NET: Registered PF_ALG protocol family [ 0.645796] xor: measuring software checksum speed [ 0.659632] 8regs : 712 MB/sec [ 0.673527] 8regs_prefetch : 710 MB/sec [ 0.687386] 32regs : 711 MB/sec [ 0.701289] 32regs_prefetch : 709 MB/sec [ 0.701306] xor: using function: 8regs (712 MB/sec) [ 0.701326] async_tx: api initialized (async) [ 0.743397] Serial: 8250/16550 driver, 6 ports, IRQ sharing disabled [ 0.761274] sun8i-mixer 5100000.mixer: Adding to iommu group 0 [ 0.762086] sun8i-mixer 5200000.mixer: Adding to iommu group 0 [ 0.772085] PPP generic driver version 2.4.2 [ 0.772522] PPP BSD Compression module registered [ 0.772541] PPP Deflate Compression module registered [ 0.772553] NET: Registered PF_PPPOX protocol family [ 0.772610] SLIP: version 0.8.4-NET3.019-NEWTTY (dynamic channels, max=256) (6 bit encapsulation enabled). [ 0.772629] CSLIP: code copyright 1989 Regents of the University of California. [ 0.772638] SLIP linefill/keepalive option. [ 0.774534] usbcore: registered new interface driver uas [ 0.774650] usbcore: registered new interface driver usb-storage [ 0.774924] usbcore: registered new interface driver ch341 [ 0.775004] usbserial: USB Serial support registered for ch341-uart [ 0.776114] UDC core: g_ether: couldn't find an available UDC [ 0.776714] i2c_dev: i2c /dev entries driver [ 0.779899] softdog: initialized. soft_noboot=0 soft_margin=60 sec soft_panic=0 (nowayout=0) [ 0.779926] softdog: soft_reboot_cmd=<not set> soft_active_on_boot=0 [ 0.781409] device-mapper: ioctl: 4.47.0-ioctl (2022-07-28) initialised: dm-devel@redhat.com [ 0.784439] ledtrig-cpu: registered to indicate activity on CPUs [ 0.788196] usbcore: registered new interface driver snd-usb-audio [ 0.791267] pktgen: Packet Generator for packet performance testing. Version: 2.75 [ 0.792034] ipip: IPv4 and MPLS over IPv4 tunneling driver [ 0.793522] gre: GRE over IPv4 demultiplexor driver [ 0.793541] ip_gre: GRE over IPv4 tunneling driver [ 0.799291] NET: Registered PF_INET6 protocol family [ 0.804235] Segment Routing with IPv6 [ 0.804438] In-situ OAM (IOAM) with IPv6 [ 0.804669] NET: Registered PF_PACKET protocol family [ 0.804710] NET: Registered PF_KEY protocol family [ 0.805062] 8021q: 802.1Q VLAN Support v1.8 [ 0.805652] sctp: Hash tables configured (bind 512/512) [ 0.806479] tipc: Activated (version 2.0.0) [ 0.807089] NET: Registered PF_TIPC protocol family [ 0.807429] tipc: Started in single node mode [ 0.808374] Key type .fscrypt registered [ 0.808395] Key type fscrypt-provisioning registered [ 0.810354] Key type encrypted registered [ 0.863941] sun4i-drm display-engine: Adding to iommu group 0 [ 0.878031] sun4i-drm display-engine: bound 5100000.mixer (ops 0xffffffff80b01458) [ 0.883298] sun4i-drm display-engine: bound 5200000.mixer (ops 0xffffffff80b01458) [ 0.883345] sun4i-drm display-engine: bound 5460000.tcon-top (ops 0xffffffff80b057d8) [ 0.884578] sun4i-drm display-engine: No panel or bridge found... RGB output disabled [ 0.884614] sun4i-drm display-engine: bound 5461000.lcd-controller (ops 0xffffffff80afe3c0) [ 0.885415] sun4i-drm display-engine: bound 5470000.lcd-controller (ops 0xffffffff80afe3c0) [ 0.886388] sun8i-dw-hdmi 5500000.hdmi: Detected HDMI TX controller v2.12a with HDCP (sun8i_dw_hdmi_phy) [ 0.887499] sun8i-dw-hdmi 5500000.hdmi: registered DesignWare HDMI I2C bus driver [ 0.888126] sun4i-drm display-engine: bound 5500000.hdmi (ops 0xffffffff80b00528) [ 0.889925] [drm] Initialized sun4i-drm 1.0.0 20150629 for display-engine on minor 0 [ 0.900271] input: 2009800.keys as /devices/platform/soc/2009800.keys/input/input0 [ 0.918653] sunxi-wdt 6011000.watchdog: Watchdog enabled (timeout=16 sec, nowayout=0) [ 0.964473] sun20i-d1-pinctrl 2000000.pinctrl: initialized sunXi PIO driver [ 0.968642] printk: console [ttyS0] disabled [ 0.989174] 2500000.serial: ttyS0 at MMIO 0x2500000 (irq = 209, base_baud = 1500000) is a 16550A [ 1.931142] printk: console [ttyS0] enabled [ 1.958274] 2500400.serial: ttyS1 at MMIO 0x2500400 (irq = 210, base_baud = 1500000) is a 16550A [ 1.971932] spi-nand spi0.0: Macronix SPI NAND was found. [ 1.977447] spi-nand spi0.0: 256 MiB, block size: 128 KiB, page size: 2048, OOB size: 64 [ 1.989585] 4 fixed-partitions partitions found on MTD device spi0.0 [ 1.996036] Creating 4 MTD partitions on "spi0.0": [ 2.000888] 0x000000000000-0x000000100000 : "boot0" [ 2.012150] 0x000000100000-0x000000400000 : "uboot" [ 2.030040] 0x000000400000-0x000000500000 : "secure_storage" [ 2.042181] 0x000000500000-0x000010000000 : "sys" [ 2.756834] random: crng init done [ 2.848410] dwmac-sun8i 4500000.ethernet: IRQ eth_wake_irq not found [ 2.854891] dwmac-sun8i 4500000.ethernet: IRQ eth_lpi not found [ 2.861560] dwmac-sun8i 4500000.ethernet: PTP uses main clock [ 2.867444] dwmac-sun8i 4500000.ethernet: Current syscon value is not the default 50006 (expect 0) [ 2.876892] dwmac-sun8i 4500000.ethernet: No HW DMA feature register supported [ 2.884221] dwmac-sun8i 4500000.ethernet: RX Checksum Offload Engine supported [ 2.891498] dwmac-sun8i 4500000.ethernet: COE Type 2 [ 2.896565] dwmac-sun8i 4500000.ethernet: TX Checksum insertion supported [ 2.903466] dwmac-sun8i 4500000.ethernet: Normal descriptors [ 2.909191] dwmac-sun8i 4500000.ethernet: Chain mode enabled [ 2.931798] pcf857x 1-0038: probed [ 2.945141] sunxi-mmc 4020000.mmc: Got CD GPIO [ 2.952323] sunxi-mmc 4021000.mmc: allocated mmc-pwrseq [ 2.962105] phy phy-4100400.phy.1: Changing dr_mode to 1 [ 2.970229] ehci-platform 4200000.usb: EHCI Host Controller [ 2.977197] usb_phy_generic usb_phy_generic.2.auto: dummy supplies not allowed for exclusive requests [ 2.988506] sunxi-mmc 4020000.mmc: initialized, max. request size: 2047 KB, uses new timings mode [ 2.997607] sunxi-mmc 4021000.mmc: initialized, max. request size: 2047 KB, uses new timings mode [ 3.008127] ohci-platform 4200400.usb: Generic Platform OHCI controller [ 3.015225] ehci-platform 4200000.usb: new USB bus registered, assigned bus number 1 [ 3.023483] ohci-platform 4200400.usb: new USB bus registered, assigned bus number 2 [ 3.032571] ehci-platform 4200000.usb: irq 218, io mem 0x04200000 [ 3.039264] ohci-platform 4200400.usb: irq 220, io mem 0x04200400 [ 3.060790] ehci-platform 4200000.usb: USB 2.0 started, EHCI 1.00 [ 3.067393] usb usb1: New USB device found, idVendor=1d6b, idProduct=0002, bcdDevice= 6.01 [ 3.075819] usb usb1: New USB device strings: Mfr=3, Product=2, SerialNumber=1 [ 3.083176] usb usb1: Product: EHCI Host Controller [ 3.088179] usb usb1: Manufacturer: Linux 6.1.0-rc3-nezha ehci_hcd [ 3.096054] mmc1: new high speed SDIO card at address 0001 [ 3.101710] usb usb1: SerialNumber: 4200000.usb [ 3.112043] hub 1-0:1.0: USB hub found [ 3.116060] hub 1-0:1.0: 1 port detected [ 3.121446] usb usb2: New USB device found, idVendor=1d6b, idProduct=0001, bcdDevice= 6.01 [ 3.130095] mmc0: new high speed SDXC card at address 1234 [ 3.137528] mmcblk0: mmc0:1234 SA64G 58.0 GiB [ 3.142114] usb usb2: New USB device strings: Mfr=3, Product=2, SerialNumber=1 [ 3.151442] usb usb2: Product: Generic Platform OHCI controller [ 3.157643] mmcblk0: p1 p2 [ 3.160888] usb usb2: Manufacturer: Linux 6.1.0-rc3-nezha ohci_hcd [ 3.167947] usb usb2: SerialNumber: 4200400.usb [ 3.173925] hub 2-0:1.0: USB hub found [ 3.177879] hub 2-0:1.0: 1 port detected [ 4.033013] ohci-platform 4101400.usb: Generic Platform OHCI controller [ 4.039773] ehci-platform 4101000.usb: EHCI Host Controller [ 4.045549] musb-hdrc musb-hdrc.3.auto: MUSB HDRC host driver [ 4.051523] ehci-platform 4101000.usb: new USB bus registered, assigned bus number 3 [ 4.059408] musb-hdrc musb-hdrc.3.auto: new USB bus registered, assigned bus number 4 [ 4.067363] ohci-platform 4101400.usb: new USB bus registered, assigned bus number 5 [ 4.075511] ehci-platform 4101000.usb: irq 217, io mem 0x04101000 [ 4.082107] usb usb4: New USB device found, idVendor=1d6b, idProduct=0002, bcdDevice= 6.01 [ 4.090707] ohci-platform 4101400.usb: irq 219, io mem 0x04101400 [ 4.096982] usb usb4: New USB device strings: Mfr=3, Product=2, SerialNumber=1 [ 4.104278] usb usb4: Product: MUSB HDRC host driver [ 4.109297] usb usb4: Manufacturer: Linux 6.1.0-rc3-nezha musb-hcd [ 4.115550] ehci-platform 4101000.usb: USB 2.0 started, EHCI 1.00 [ 4.121720] usb usb4: SerialNumber: musb-hdrc.3.auto [ 4.128124] hub 4-0:1.0: USB hub found [ 4.132081] hub 4-0:1.0: 1 port detected [ 4.137396] usb usb3: New USB device found, idVendor=1d6b, idProduct=0002, bcdDevice= 6.01 [ 4.146375] using random self ethernet address [ 4.151051] using random host ethernet address [ 4.155661] usb usb3: New USB device strings: Mfr=3, Product=2, SerialNumber=1 [ 4.168635] usb0: HOST MAC 0a:2e:24:ef:46:b5 [ 4.173025] usb usb3: Product: EHCI Host Controller [ 4.177963] usb0: MAC 06:24:1a:84:1b:76 [ 4.181861] usb usb3: Manufacturer: Linux 6.1.0-rc3-nezha ehci_hcd [ 4.188147] g_ether gadget.0: Ethernet Gadget, version: Memorial Day 2008 [ 4.195031] usb usb3: SerialNumber: 4101000.usb [ 4.199631] g_ether gadget.0: g_ether ready [ 4.205342] hub 3-0:1.0: USB hub found [ 4.211519] clk: Not disabling unused clocks [ 4.216004] hub 3-0:1.0: 1 port detected [ 4.220024] ALSA device list: [ 4.223052] #0: sun20i-codec [ 4.227536] usb usb5: New USB device found, idVendor=1d6b, idProduct=0001, bcdDevice= 6.01 [ 4.236543] usb usb5: New USB device strings: Mfr=3, Product=2, SerialNumber=1 [ 4.243902] usb usb5: Product: Generic Platform OHCI controller [ 4.250050] usb usb5: Manufacturer: Linux 6.1.0-rc3-nezha ohci_hcd [ 4.256313] usb usb5: SerialNumber: 4101400.usb [ 4.262237] hub 5-0:1.0: USB hub found [ 4.266193] hub 5-0:1.0: 1 port detected [ 4.271278] md: Waiting for all devices to be available before autodetect [ 4.278298] md: If you don't use raid, use raid=noautodetect [ 4.284029] md: Autodetecting RAID arrays. [ 4.288170] md: autorun ... [ 4.291006] md: ... autorun DONE. [ 4.322157] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Quota mode: disabled. [ 4.331208] VFS: Mounted root (ext4 filesystem) readonly on device 179:2. [ 4.339657] devtmpfs: mounted [ 4.345575] Freeing unused kernel image (initmem) memory: 2124K [ 4.351621] Run /sbin/init as init process [ 4.355805] with arguments: [ 4.358815] /sbin/init [ 4.361565] with environment: [ 4.364716] HOME=/ [ 4.367160] TERM=linux INIT: version 3.04 booting Framebuffer /dev/fb0 not detected Boot splashscreen disabled Starting udev [ 5.300386] udevd[137]: starting version 3.2.11 [ 5.380494] udevd[138]: starting eudev-3.2.11 [ 6.117599] mtdblock: MTD device 'boot0' is NAND, please consider using UBI block devices instead. [ 6.173478] mtdblock: MTD device 'uboot' is NAND, please consider using UBI block devices instead. [ 6.185120] mtdblock: MTD device 'secure_storage' is NAND, please consider using UBI block devices instead. [ 6.279098] mtdblock: MTD device 'spi0.0' is NAND, please consider using UBI block devices instead. [ 6.297818] mtdblock: MTD device 'sys' is NAND, please consider using UBI block devices instead. [ 7.001016] EXT4-fs (mmcblk0p2): re-mounted. Quota mode: disabled. hwclock: can't open '/dev/misc/rtc': No such file or directory Fri Mar 9 12:34:56 UTC 2018 hwclock: can't open '/dev/misc/rtc': No such file or directory INIT: Entering runlevel: 5 Configuring network interfaces... ifup: unknown address type "inet" hwclock: can't open '/dev/misc/rtc': No such file or directory Starting syslogd/klogd: done OpenEmbedded nodistro.0 nezha-allwinner-d1 /dev/ttyS0 nezha-allwinner-d1 login: |
NOTE:
- the build dates of U-Boot and the kernel are wrong because Yocto enables reproducible builds by default, so although it claims to have been build back in Nov 2022, this image was actually built late Jan 2023
As this image boots, it runs U-Boot (starting at line 75 of the output above). The meta-riscv layer added a script to U-Boot (/boot.scr.uimg) that it finds and runs as part of its boot process (line 105). The script looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | # This is the default TFTP and MMC u-boot boot script # The order is as follows: # 1. TFTP load a uEnv.txt # 2. TFTP boot a fitImage # 3. MMC load a uEnv.txt # 4. MMC load a fitImage # 5. TFTP load a uImage # 6. MMC load a fitImage # Setup the DHCP for a TFTP boot setenv serverip @SERVERIP@ dhcp # See if we have a TFTP uEnv.txt file if tftpboot ${scriptaddr} uEnv.txt; then env import -t ${scriptaddr} ${filesize} run bootcmd fi; # Try to boot a fitImage from the TFTP server if tftpboot ${ramdisk_addr_r} fitImage; then bootm ${ramdisk_addr_r} fi; # See if we have a MMC uEnv.txt file if fatload ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} /uEnv.txt; then env import -t ${scriptaddr} ${filesize} run bootcmd fi; # Try to boot a fitImage from the MMC if load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} fitImage; then bootm ${ramdisk_addr_r} fi; # Fallback to a TFTP uImage if tftpboot ${kernel_addr_r} uImage; then bootm ${kernel_addr_r} - ${fdt_addr_r} fi; # Finally fallback to a MMC uImage if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} uImage; then bootm ${kernel_addr_r} - ${fdt_addr_r} fi; |
This script initializes networking and tries to download an environment and fitImage from a remote server. If those fail, then it tries to do the same from the SDcard. If you don't have a server and haven't provided its IP address to this script then these steps will fail. However, as U-Boot tries to download these items, those attempts have to timeout before it will mark those steps as having failed and proceed to the next steps. In the case where you don't have a server setup, these steps take quite a while to timeout. The boot log that I provided above shows the case where not only have I not setup a server but I also don't have an ethernet cable plugged into the Nezha board. If you do happen to have a cable plugged in (but still don't have a server setup), the boot looks a little different during the U-Boot phase, and takes even longer to timeout and move on to looking for the artifacts on the SDcard.
Here's what the U-Boot portion of the bootup looks like if you don't have a server setup, but you do have an ethernet cable plugged into the Nezha board, and you do have a DHCP server on this network:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | U-Boot 2022.10 (Nov 01 2022 - 04:27:57 +0000) Allwinner Technology DRAM: 1 GiB sunxi_set_gate: (CLK#24) unhandled Core: 54 devices, 20 uclasses, devicetree: separate WDT: Started watchdog@6011000 with servicing every 1000ms (16s timeout) MMC: mmc@4020000: 0, mmc@4021000: 1 Loading Environment from FAT... PLL reg = 0xf8216300, freq = 1200000000 Unable to read "uboot.env" from mmc0:1... In: serial@2500000 Out: serial@2500000 Err: serial@2500000 Net: Warning: ethernet@4500000 (eth0) using random MAC address - 1e:06:7c:ef:f4:0c eth0: ethernet@4500000 starting USB... Bus usb@4101000: USB EHCI 1.00 Bus usb@4101400: USB OHCI 1.0 Bus usb@4200000: USB EHCI 1.00 Bus usb@4200400: USB OHCI 1.0 scanning bus usb@4101000 for devices... 1 USB Device(s) found scanning bus usb@4101400 for devices... 1 USB Device(s) found scanning bus usb@4200000 for devices... 1 USB Device(s) found scanning bus usb@4200400 for devices... 1 USB Device(s) found scanning usb for storage devices... 0 Storage Device(s) found Hit any key to stop autoboot: 2 ^H^H^H 1 ^H^H^H 0 PLL reg = 0xf8216300, freq = 1200000000 switch to partitions #0, OK mmc0 is current device Scanning mmc 0:1... Found U-Boot script /boot.scr.uimg 1249 bytes read in 1 ms (1.2 MiB/s) ## Executing script at 41900000 BOOTP broadcast 1 BOOTP broadcast 2 DHCP client bound to address 10.0.0.51 (987 ms) *** Warning: no boot file name; using '0A000033.img' Using ethernet@4500000 device TFTP from server 127.0.0.1; our IP address is 10.0.0.51; sending through gateway 10.0.0.1 Filename '0A000033.img'. Load address: 0x42000000 Loading: *^HT T T T T T T T T T Retry count exceeded; starting again Using ethernet@4500000 device TFTP from server 127.0.0.1; our IP address is 10.0.0.51; sending through gateway 10.0.0.1 Filename 'uEnv.txt'. Load address: 0x41900000 Loading: *^HT T T T T T T T T T Retry count exceeded; starting again Using ethernet@4500000 device TFTP from server 127.0.0.1; our IP address is 10.0.0.51; sending through gateway 10.0.0.1 Filename 'fitImage'. Load address: 0x41c00000 Loading: *^HT T T T T T T T T T Retry count exceeded; starting again 322 bytes read in 1 ms (314.5 KiB/s) 5431248 bytes read in 898 ms (5.8 MiB/s) ## Loading kernel from FIT Image at 41c00000 ... Using 'conf-1' configuration Trying 'kernel-1' kernel subimage Description: Linux kernel Type: Kernel Image Compression: gzip compressed Data Start: 0x41c0010c Data Size: 5428090 Bytes = 5.2 MiB Architecture: RISC-V OS: Linux Load Address: 0x40200000 Entry Point: 0x40200000 Hash algo: sha256 Hash value: c0897b7ecc8c79a67ffd0b71af8a90e3bdeb028c9953a13a8a4b2c0dab4e3797 Verifying Hash Integrity ... sha256+ OK ## Flattened Device Tree blob at 7fd3e180 Booting using the fdt blob at 0x7fd3e180 Working FDT set to 7fd3e180 Uncompressing Kernel Image Loading Device Tree to 0000000042df2000, end 0000000042dffc27 ... OK Working FDT set to 42df2000 Starting kernel ... |
These timeouts take a very long time to expire (longer than the ones with the cable unplugged). While this is a way to do development, you might want to turn this off if you don't plan on using a server this way, or for production. One way to do this would be to create a bbappend in one of your own layers:
1 2 3 4 5 | └── recipes-bsp └── u-boot ├── files │ └── tftp-mmc-boot.txt └── u-boot-nezha%.bbappend |
set the bbappend to contain:
1 | FILESEXTRAPATHS:prepend := "${THISDIR}/files:" |
and modify the script so that it only contains:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # This is the default TFTP and MMC u-boot boot script # The order is as follows: # 3. MMC load a uEnv.txt # 4. MMC load a fitImage # 6. MMC load a fitImage # See if we have a MMC uEnv.txt file if fatload ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} /uEnv.txt; then env import -t ${scriptaddr} ${filesize} run bootcmd fi; # Try to boot a fitImage from the MMC if load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} fitImage; then bootm ${ramdisk_addr_r} fi; # Finally fallback to a MMC uImage if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} uImage; then bootm ${kernel_addr_r} - ${fdt_addr_r} fi; |
Build, flash, apply power. Now when the board boots, U-Boot won't try to get artifacts over the network and your boot will proceed much quicker.
GPIO
The Nezha-D1 comes in a (roughly) Raspberry Pi form with a (roughly) Raspberry Pi 40-pin header. Using an SBC in a project often involves connecting devices to its header pins and then writing or adding software to manipulate the state of those pins. Learning how to "find" those pins programmatically is an important step towards working with any board. Before writing code to interact with the pins, we can experiment with them on the cmdline.
Starting with release 4.18, the Linux kernel revamped its GPIO subsystem. Therefore the interfaces and procedures to work with GPIOs changed. The current way (as of this writing) to interact with GPIOs from user-space involves working with libgpiod and its tools. If you search for instructions to work with GPIOs on the internet, you might unknowingly come across old information. Under the new scheme individual GPIOs are called "lines". Each line is connected to and controlled by a "gpio chip".
To start with, we need to find a diagram or schematic describing each of the pins on the 40-pin header. Luckily the schematic for the Nezha board is available and can be found here. Looking at the schematic, on page 7/9 we find the details of the 40-pin header (middle, lower, titled: IO EXPAND):
This information pairs the pin number with the SoC's pin name. For example: pin 3, labeled "GPIO1/TWI2-SDA", is connected to SoC pin PB1-S. Looking at the SoC names we find: PBx-S, PCx-S, PDx-S, and PPx. From experience, some SoCs do I/O via ports and have a couple ports to which I/O is connected. So I'm going to guess that PB, for example, stands for "Port B" and that this board exposes portions of the SoC's ports labeled B to D ... and P? At this point I'm fairly sure the P stands for "port", the next letter specifies which port (numbered alphabetically), and the next value is the specific pin of the given port. For example: PD15-S is the 16th pin (counting from 0) of Port D.
I'm not sure what the "-S" means. Looking back at the schematic:
it explains that the "-S" values are simply the raw pins after passing through a resistor. So for the rest of this I'll just drop the "-S" and treat the two synonymously.
On the 40-pin header we don't see any pins from port A, but we see:
- ports B
- PB0 (pin 5)
- PB1 (pin 3)
- PB3 (pin 38)
- PB4 (pin 40)
- PB5 (pin 12)
- PB6 (pin 35)
- PB8 (pin 8)
- PB9 (pin 10)
- PB12 (pin 15)
- C
- PC1 (pin 31)
- D
- PD10 (pin 24)
- PD11 (pin 23)
- PD12 (pin 19)
- PD13 (pin 21)
- PD14 (pin 27)
- PD15 (pin 29)
- PD22 (pin 7)
- P
- PP0 (pin 13)
- PP1 (pin 16)
- PP2 (pin 18)
- PP3 (pin 38)
- PP4 (pin 40)
- PP5 (pin 28)
- PP6 (pin 37)
- PP7 (pin 11)
- 3V3
- pin 1
- pin 17
- 5V
- pin 2
- pin 4
- GND
- pin 9
- pin 25
- pin 39
- pin 6
- pin 14
- pin 20
- pin 30
- pin 34
Strangely, pins 32, 33, and 36 aren't connected to anything.
By this point I've already run some of the libgpiod commands. Specifically I've run:
1 2 3 | root@nezha-allwinner-d1:~# gpiodetect gpiochip0 [2000000.pinctrl] (224 lines) gpiochip1 [pcf8574a] (8 lines) |
This tells me that Linux is aware there are 2 gpio chips on this SoC. Digging a little deeper:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | root@nezha-allwinner-d1:~# gpioinfo gpiochip0 - 224 lines: line 0: unnamed unused input active-high line 1: unnamed unused input active-high line 2: unnamed unused input active-high line 3: unnamed unused input active-high line 4: unnamed unused input active-high line 5: unnamed unused input active-high line 6: unnamed unused input active-high line 7: unnamed unused input active-high line 8: unnamed unused input active-high line 9: unnamed unused input active-high line 10: unnamed unused input active-high line 11: unnamed unused input active-high line 12: unnamed unused input active-high line 13: unnamed unused input active-high line 14: unnamed unused input active-high line 15: unnamed unused input active-high line 16: unnamed unused input active-high line 17: unnamed unused input active-high line 18: unnamed unused input active-high line 19: unnamed unused input active-high line 20: unnamed unused input active-high line 21: unnamed unused input active-high line 22: unnamed unused input active-high line 23: unnamed unused input active-high line 24: unnamed unused input active-high line 25: unnamed unused input active-high line 26: unnamed unused input active-high line 27: unnamed unused input active-high line 28: unnamed unused input active-high line 29: unnamed unused input active-high line 30: unnamed unused input active-high line 31: unnamed unused input active-high line 32: unnamed kernel input active-high [used] line 33: unnamed kernel input active-high [used] line 34: unnamed "interrupt" input active-high [used] line 35: unnamed unused input active-high line 36: unnamed unused input active-high line 37: unnamed unused input active-high line 38: unnamed unused input active-high line 39: unnamed unused input active-high line 40: unnamed kernel input active-high [used] line 41: unnamed kernel input active-high [used] line 42: unnamed unused input active-high line 43: unnamed unused input active-high line 44: unnamed unused input active-high line 45: unnamed unused input active-high line 46: unnamed unused input active-high line 47: unnamed unused input active-high line 48: unnamed unused input active-high line 49: unnamed unused input active-high line 50: unnamed unused input active-high line 51: unnamed unused input active-high line 52: unnamed unused input active-high line 53: unnamed unused input active-high line 54: unnamed unused input active-high line 55: unnamed unused input active-high line 56: unnamed unused input active-high line 57: unnamed unused input active-high line 58: unnamed unused input active-high line 59: unnamed unused input active-high line 60: unnamed unused input active-high line 61: unnamed unused input active-high line 62: unnamed unused input active-high line 63: unnamed unused input active-high line 64: unnamed kernel input active-high [used] line 65: unnamed unused input active-high line 66: unnamed kernel input active-high [used] line 67: unnamed kernel input active-high [used] line 68: unnamed kernel input active-high [used] line 69: unnamed kernel input active-high [used] line 70: unnamed kernel input active-high [used] line 71: unnamed kernel input active-high [used] line 72: unnamed unused input active-high line 73: unnamed unused input active-high line 74: unnamed unused input active-high line 75: unnamed unused input active-high line 76: unnamed unused input active-high line 77: unnamed unused input active-high line 78: unnamed unused input active-high line 79: unnamed unused input active-high line 80: unnamed unused input active-high line 81: unnamed unused input active-high line 82: unnamed unused input active-high line 83: unnamed unused input active-high line 84: unnamed unused input active-high line 85: unnamed unused input active-high line 86: unnamed unused input active-high line 87: unnamed unused input active-high line 88: unnamed unused input active-high line 89: unnamed unused input active-high line 90: unnamed unused input active-high line 91: unnamed unused input active-high line 92: unnamed unused input active-high line 93: unnamed unused input active-high line 94: unnamed unused input active-high line 95: unnamed unused input active-high line 96: unnamed unused input active-high line 97: unnamed unused input active-high line 98: unnamed unused input active-high line 99: unnamed unused input active-high line 100: unnamed unused input active-high line 101: unnamed unused input active-high line 102: unnamed unused input active-high line 103: unnamed unused input active-high line 104: unnamed unused input active-high line 105: unnamed unused input active-high line 106: unnamed kernel input active-high [used] line 107: unnamed kernel input active-high [used] line 108: unnamed kernel input active-high [used] line 109: unnamed kernel input active-high [used] line 110: unnamed kernel input active-high [used] line 111: unnamed kernel input active-high [used] line 112: unnamed kernel input active-high [used] line 113: unnamed unused input active-high line 114: unnamed unused input active-high line 115: unnamed "usbvbus" output active-high [used] line 116: unnamed "usb0_vbus_det" input active-high [used] line 117: unnamed "usb0_id_det" input active-high [used] line 118: unnamed unused input active-high line 119: unnamed unused input active-high line 120: unnamed unused input active-high line 121: unnamed unused input active-high line 122: unnamed unused input active-high line 123: unnamed unused input active-high line 124: unnamed unused input active-high line 125: unnamed unused input active-high line 126: unnamed unused input active-high line 127: unnamed unused input active-high line 128: unnamed kernel input active-high [used] line 129: unnamed kernel input active-high [used] line 130: unnamed kernel input active-high [used] line 131: unnamed kernel input active-high [used] line 132: unnamed kernel input active-high [used] line 133: unnamed kernel input active-high [used] line 134: unnamed kernel input active-high [used] line 135: unnamed kernel input active-high [used] line 136: unnamed kernel input active-high [used] line 137: unnamed kernel input active-high [used] line 138: unnamed unused input active-high line 139: unnamed kernel input active-high [used] line 140: unnamed kernel input active-high [used] line 141: unnamed kernel input active-high [used] line 142: unnamed kernel input active-high [used] line 143: unnamed kernel input active-high [used] line 144: unnamed unused input active-high line 145: unnamed unused input active-high line 146: unnamed unused input active-high line 147: unnamed unused input active-high line 148: unnamed unused input active-high line 149: unnamed unused input active-high line 150: unnamed unused input active-high line 151: unnamed unused input active-high line 152: unnamed unused input active-high line 153: unnamed unused input active-high line 154: unnamed unused input active-high line 155: unnamed unused input active-high line 156: unnamed unused input active-high line 157: unnamed unused input active-high line 158: unnamed unused input active-high line 159: unnamed unused input active-high line 160: unnamed kernel input active-high [used] line 161: unnamed kernel input active-high [used] line 162: unnamed kernel input active-high [used] line 163: unnamed kernel input active-high [used] line 164: unnamed kernel input active-high [used] line 165: unnamed kernel input active-high [used] line 166: unnamed "cd" input active-high [used] line 167: unnamed unused input active-high line 168: unnamed unused input active-high line 169: unnamed unused input active-high line 170: unnamed unused input active-high line 171: unnamed unused input active-high line 172: unnamed unused input active-high line 173: unnamed unused input active-high line 174: unnamed unused input active-high line 175: unnamed unused input active-high line 176: unnamed unused input active-high line 177: unnamed unused input active-high line 178: unnamed unused input active-high line 179: unnamed unused input active-high line 180: unnamed unused input active-high line 181: unnamed unused input active-high line 182: unnamed unused input active-high line 183: unnamed unused input active-high line 184: unnamed unused input active-high line 185: unnamed unused input active-high line 186: unnamed unused input active-high line 187: unnamed unused input active-high line 188: unnamed unused input active-high line 189: unnamed unused input active-high line 190: unnamed unused input active-high line 191: unnamed unused input active-high line 192: unnamed kernel input active-high [used] line 193: unnamed kernel input active-high [used] line 194: unnamed kernel input active-high [used] line 195: unnamed kernel input active-high [used] line 196: unnamed kernel input active-high [used] line 197: unnamed kernel input active-high [used] line 198: unnamed kernel input active-high [used] line 199: unnamed kernel input active-high [used] line 200: unnamed kernel input active-high [used] line 201: unnamed kernel input active-high [used] line 202: unnamed unused input active-high line 203: unnamed unused input active-high line 204: unnamed "reset" output active-low [used] line 205: unnamed unused input active-high line 206: unnamed unused input active-high line 207: unnamed unused input active-high line 208: unnamed unused input active-high line 209: unnamed unused input active-high line 210: unnamed unused input active-high line 211: unnamed unused input active-high line 212: unnamed unused input active-high line 213: unnamed unused input active-high line 214: unnamed unused input active-high line 215: unnamed unused input active-high line 216: unnamed unused input active-high line 217: unnamed unused input active-high line 218: unnamed unused input active-high line 219: unnamed unused input active-high line 220: unnamed unused input active-high line 221: unnamed unused input active-high line 222: unnamed unused input active-high line 223: unnamed unused input active-high gpiochip1 - 8 lines: line 0: unnamed unused input active-high line 1: unnamed unused input active-high line 2: unnamed unused input active-high line 3: unnamed unused input active-high line 4: unnamed unused input active-high line 5: unnamed unused input active-high line 6: unnamed unused input active-high line 7: unnamed unused input active-high |
This doesn't help us too much, at least not immediately. Sometimes, if we're lucky, these lines will have more meaningful names (which are specified in the board's device-tree) but that's not the case here. We have to dig a little deeper to figure out how these lines relate to the port names we identified in the schematic.
Looking through the source for the kernel that we're using, we come across: https://github.com/smaeul/linux/blob/d1/all/drivers/pinctrl/sunxi/pinctrl-sunxi.h#L19
This looks interesting. Previously gpioinfo told us that gpiochip0 has 224 lines (0 to 223). Looking at this header file (drivers/pinctrl/sunxi/pinctrl-sunxi.h) it looks like, on sunxi SoCs, each port has 32 pins. 224 is evenly divisible by 32, which implies this SoC has 7 ports: PortA through PortG. At this point I'll take a guess that we can now find all the PBx to PDx pins that are described in the schematic. For example, if we want to work with pin 40 on the header, aka GPIO25-I2S2-DOUT, aka PB4, I'm betting we'll find it on gpiochip0 line 36 since PortB starts at offset 32, and 32 + 4 = 36. PC1, on the oher hand, would be on gpiochip0 line 65 since PortC starts at offset 64.
We can wire up a simple circuit to test this theory:
NOTE:- I have a bunch of LEDs that have the resistor build-in, hence the lack of resistor
Then we can test with:
1 | root@nezha-allwinner-d1:~# gpioset gpiochip0 36=1 |
and we get:
We can move the wire from pin 40 to other pins and test our calculations.
But what about those "Port P" pins? In the schematic we found some pins labeled PP[0-7]. The kernel only has support for I/O up to Port N (in theory), and this specific SoC only appears to have I/O up to Port G (inclusive, according to gpioinfo). Looking again at the schematic (page 7/9, bottom left):
The designers of this board have decided to use a port expander for 8 of the pins in the 40-pin header. A port expander is a chip that allows you access to a larger number of IOs using a smaller number of SoC IOs. This IO expander, the PCF857x, is an I2C device. If you look carefully at this diagram and the schematic diagram of the 40-pin header, you'll notice that 2 pins from the 40-pin header (PB0 and PB1) are used to control this PCF857x and these 2 pins can be used to control the 8 PPx pins!
Now that we understand how these PPx pins are accessed, how do we go about accessing them? The good news is that we don't have to figure out how to access them since Linux already takes care of this for us. The Linux kernel already has a driver for the PCF857x (drivers/gpio/gpio-pcf857x.c) so all that's needed to get this to work is some glue logic in the device tree to associate PB0 to the PCF857x's SCK signal and PB1 with SDA. Thankfully this is already done for us in the Nezha's device tree (https://github.com/smaeul/linux/blob/d1/all/arch/riscv/boot/dts/allwinner/sun20i-d1-nezha.dts#L116):
If we look back at what the kernel told us about its GPIO setup we see:
1 2 3 4 5 6 7 | root@nezha-allwinner-d1:~# gpioinfo gpiochip0 - 224 lines: … line 32: unnamed kernel input active-high [used] line 33: unnamed kernel input active-high [used] line 34: unnamed "interrupt" input active-high [used] … |
This meshes exactly with what we've just discovered: gpiochip0 lines 32 and 33 are PB0 and PB1 (since the offset for Port B is 32 on this device). We also see that line 34, PB2, is marked as an interrupt. gpioinfo marks all 3 lines as being in-use (and therefore not available for us to poke and peek).
Now that we've got all of that figured out, how do we access the PPx pins that come off the GPIO expander? It's probably not a coincidence that gpioinfo told us that there are 8 lines on gpiochip1 and that the PCF857x GPIO expander has 8 pins:
1 2 3 4 5 6 7 8 9 10 | root@nezha-allwinner-d1:~# gpioinfo gpiochip1 gpiochip1 - 8 lines: line 0: unnamed unused input active-high line 1: unnamed unused input active-high line 2: unnamed unused input active-high line 3: unnamed unused input active-high line 4: unnamed unused input active-high line 5: unnamed unused input active-high line 6: unnamed unused input active-high line 7: unnamed unused input active-high |
If we look at the Linux kernel's driver for the PCF857x (drivers/gpio/gpio-pcf857x.c) we find (https://github.com/smaeul/linux/blob/d1/all/drivers/gpio/gpio-pcf857x.c#L376):
This proves the connection between GPIO expanders and gpiochips. In the Linux kernel, GPIO expanders are exposed as gpiochips. In fact, if we looked carefully at the output of gpiodetect we would have noticed that the kernel was already telling us that gpiochip1 is connected to the PCF857x:
1 2 3 | root@nezha-allwinner-d1:~# gpiodetect gpiochip0 [2000000.pinctrl] (224 lines) gpiochip1 [pcf8574a] (8 lines) |
Let's test this. I'm going to arbitrarily pick PP6, pin 37, and wire up the following test:
run:
1 | root@nezha-allwinner-d1:~# gpioset gpiochip1 6=1 |
and:
nothing happens? It looks like the LED didn't come on. If you look closely, and possibly also dim the room lights, you'll see that the LED did, in fact, come on, though very dimly. At first this seems strange since the schematic shows the PCF857x as being connected to 3V3, so its output should be 3V3, and that should be enough to light the LED more visibly.
I'm not an electrical engineer so I don't know all the details of "why", but the short version is: the PCF857x is better at making zeros than it is at making ones. Trying to drive the LED by connecting the cathode (aka negative/short) of the LED to ground and the anode (aka positive/long pin) to the GPIO isn't going to work as well as we would expect. Instead, when working with the PCF957x, connect the cathode (aka negative/short pin) to the GPIO and the anode (positive/long pin) to 3V3 (we connect to 3V3 and not 5V since the PCF847x is using 3V3 for VDD, see the schematic for the Nezha board):
This time we perform:
1 | root@nezha-allwinner-d1:~# gpioset gpiochip1 6=0 |
and get what we're expecting (a brightly lit led):
As you've probably noticed, the logic with this wiring is backwards. If PP6 is set to 1 then both wires feeding the LED will be giving it 3V3, turning it off. But if you set PP6 to 0 then there will be a potential across the LED and it will turn on. Using the GPIO for the zero, and holding the other side of the LED at 3V3 is why we had to turn the LED around and connect the GPIO to the anode.
RGB LED
Another neat thing about the Nezha board is that it has a user-controlled RGB LED (instead of just a regular single-colour LED). Interestingly enough, the AllWinner SoC has an RGB LED controller (called ledc), and the Linux kernel has a driver for it: CONFIG_LEDS_SUN50I_A100.
If you used the exact SHA1 checkout for your build as I used above, then you'll find a small issue with the meta-riscv layer in that it is using an older name for the driver in its defconfig. As a result, support for the RGB LED is not enabled correctly. That's been fixed, so upgrading to a more recent commit should work better.
With a correctly-working kernel, in user-space you can navigate (on the Nezha board) to /sys/class/leds/rgb:status and play with the RGB LED.
1 2 3 4 5 6 7 8 9 10 11 | root@nezha-allwinner-d1:/sys/class/leds/rgb:status# ls -l total 0 -rw-r--r-- 1 root root 4096 Jan 30 19:22 brightness lrwxrwxrwx 1 root root 0 Jan 30 20:34 device -> ../../../2008000.led-controller -r--r--r-- 1 root root 4096 Jan 30 20:34 max_brightness -r--r--r-- 1 root root 4096 Jan 30 20:34 multi_index -rw-r--r-- 1 root root 4096 Jan 30 19:50 multi_intensity drwxr-xr-x 2 root root 0 Jan 30 20:34 power lrwxrwxrwx 1 root root 0 Dec 20 13:15 subsystem -> ../../../../../../class/leds -rw-r--r-- 1 root root 0 Jan 30 19:18 trigger -rw-r--r-- 1 root root 4096 Dec 20 13:15 uevent |
First, take a look at the output from multi_index:
1 2 | root@nezha-allwinner-d1:/sys/class/leds/rgb:status# cat multi_index red green blue |
This tells you the expected order of LED values. Check the max_brightness, set the brightness to something small (no need to burn your retinas), and specify your red, green, and blue values:
1 2 3 4 | root@nezha-allwinner-d1:/sys/class/leds/rgb:status# cat max_brightness 255 root@nezha-allwinner-d1:/sys/class/leds/rgb:status# echo 15 > brightness root@nezha-allwinner-d1:/sys/class/leds/rgb:status# echo "0 0 255" > multi_intensity |
Your RGB LED will now be blue:
You can play with other combinations to your heart's content, or you could download and run this little script I wrote to randomly set the values for you continuously (run it in the background).
Another neat thing to do with a user-led is to ask the kernel to control it to provide various types of feedback. For example, you can set the LED to provide a "heartbeat" to prove your board is alive:
1 2 3 4 | root@nezha-allwinner-d1:/sys/class/leds/rgb:status# cat trigger [none] kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot mtd nand-disk heartbeat cpu cpu0 activity panic netdev mmc0 mmc1 root@nezha-allwinner-d1:/sys/class/leds/rgb:status# echo "20 0 20" > multi_intensity root@nezha-allwinner-d1:/sys/class/leds/rgb:status# echo heartbeat > trigger |
Interestingly, in trigger mode the global brightness doesn't seem to have any effect. Brightness is set by the individual values of multi_intensity. Not sure if that's a bug or a feature.