后面会发布针对 dd 等普通烧录工具的 img
Best posts made by liangdi
-
[Happy Hacking Nezha Board] 开始裸奔吧,少年
每一个程序员都有一个 OS 梦! - Liangdi
前言
我属于提前批拿到哪吒开发板的,兴奋之余开始研究如何去运行自己的裸机程序,美其名曰:操作系统.
和 mcu 不一样, sbc 级别的 cpu 跑起来要复杂的多,不过好在系统级别的领域,不同的软件分工明确, 我们的裸机程序作为 kernel 部分,等着被引导就好.
尽管 sbc 的系统很复杂, 不过要跑起我们的小小的代码,我们刚开始关心的东西不必要很多.
走出第一步,才能看到后面的广阔天空.
由于没有自己的 OS , 这里用 rt-thread 的 rt-smart 来作为实验验证对象.
uboot
和我们接触的第一个对象就是 uboot , uboot 是哪吒开发板的 bootloader,所以我们要和他搞好关系,了解他,才能让他帮我们完成 kernel 的引导.
哪吒开发板的引导路径大致是这样: BROOM -> spl -> uboot -> [nand | mmc]
通过简单的把玩,发现以下规律, BROOM 中的一级 bootloader 会检测 mmc 和 nand 设备, 如果存在 mmc 设备就会 load mmc boot 分区中的 spl 继续工作, nand 同理.
开发板上有 256MB 的 nand flash, 可以有足够的空间存放我们的程序了, 所以就不考虑 mmc 了.
哪吒的 uboot 和 nand
开发板,接上串口工具,上电,串口中就可以看到系统启动信息了, 如果你什么都不操作就会进入 tina 环境了, 所以开机的时候,连按 s 键盘(和 PC 开机按 F2或者 F10 一样吧) 就可以进入 uboot 环境
如下界面:
先用 mtdparts 查看 nand 信息
mdtparts default mtdparts # 输出如下: device nand0 <nand>, # parts = 4 #: name size offset mask_flags 0: boot0 0x00100000 0x00000000 1 1: uboot 0x00300000 0x00100000 1 2: secure_storage 0x00100000 0x00400000 1 3: sys 0x0fb00000 0x00500000 0 active partition: nand0,0 - (boot0) 0x00100000 @ 0x00000000 defaults: mtdids : nand0=nand mtdparts: mtdparts=nand:[email protected](boot0)ro,[email protected](uboot)ro,[email protected](secure_storage)ro,-(sys)
从上面可以看到, nand 有四个分区, 前面两个 bootloader , 第三 secure_storage 和我们也没有什么关系, 第四个分区 sys 就是保存用户 os 的地方, 目前就是 tina linux 系统.
查看一下 sys 中的信息
ubi part sys ubi info l # 输出如下: Volume information dump: vol_id 0 reserved_pebs 1 alignment 1 data_pad 0 vol_type 4 name_len 3 usable_leb_size 258048 used_ebs 1 used_bytes 258048 last_eb_bytes 258048 corrupted 0 upd_marker 0 name mbr Volume information dump: vol_id 1 reserved_pebs 1 alignment 1 data_pad 0 vol_type 3 name_len 13 usable_leb_size 258048 used_ebs 1 used_bytes 258048 last_eb_bytes 258048 corrupted 0 upd_marker 0 name boot-resource Volume information dump: vol_id 2 reserved_pebs 1 alignment 1 data_pad 0 vol_type 3 name_len 3 usable_leb_size 258048 used_ebs 1 used_bytes 258048 last_eb_bytes 258048 corrupted 0 upd_marker 0 name env Volume information dump: vol_id 3 reserved_pebs 1 alignment 1 data_pad 0 vol_type 3 name_len 10 usable_leb_size 258048 used_ebs 1 used_bytes 258048 last_eb_bytes 258048 corrupted 0 upd_marker 0 name env-redund Volume information dump: vol_id 4 reserved_pebs 29 alignment 1 data_pad 0 vol_type 3 name_len 4 usable_leb_size 258048 used_ebs 29 used_bytes 7483392 last_eb_bytes 258048 corrupted 0 upd_marker 0 name boot ...
我们看到了一些熟悉的信息,系统镜像的分区表, 就是 tina sdk 打包出来的产物.
那么 uboot 如何引导 nand 中的系统的呢?
使用 printenv 查看一下 uboot 的环境变量,下面列出重要的部分:boot_normal=sunxi_flash read 45000000 ${boot_partition};bootm 45000000 boot_partition=boot bootcmd=run setargs_nand_ubi boot_normal [email protected]_0:[email protected]_1:[email protected]_2:[email protected]_3:[email protected]_4:[email protected]_5:[email protected]_6:[email protected]_7:[email protected]_8: root_partition=rootfs setargs_nand_ubi=setenv bootargs ubi.mtd=${mtd_name} ubi.block=0,${root_partition} earlyprintk=${earlyprintk} clk_ignore_unused initcall_debug=${initcall_debug} console=${console} loglevel=${loglevel} root=${nand_root} rootfstype=${rootfstype} init=${init} partitions=${partitions} cma=${cma} snum=${snum} mac_addr=${mac} wifi_mac=${wifi_mac} bt_mac=${bt_mac} specialstr=${specialstr} gpt=1 ubi_attach_mtdnum=3
就以上这几行就可以了, 对我们来说关键作用的只有前 3 行.
bootcmd
这个是 uboot 启动时候执行的变量, 内容是run setargs_nand_ubi
和boot_normal
其中
setargs_nand_ubi
是设置bootargs
的, 是 Linux 关心的东西.
我们主要看boot_normal
boot_normal
大致含义是 flash 工具读取${boot_partition}
(解析后是boot
) 位置的数据到内存0x45000000
的位置, 然后bootm
引导0x45000000
位置的内核.所以,简单的方法就是我们把我们自己的 OS 程序,写入到 nand 中 boot 分区的位置,理论上就可以了.
构建 nand 和引导自己的系统
起初本来想用 xboot 的 xfel 工具将数据写入 nand, 然后发现没有实现,所以先跳过, 等后续支持了就会更方便了.
tina sdk 中
device/config/chips/d1/configs/nezha_min/sys_partition.fex
这个文件是pack
的配置信息 , 根据文件知道pack
命令会把 boot.img 打包到 nand 的 boot 分区, 这个就是我们所要的,所以最简单的方法就是把我们自己的 bin 文件替换调 boot.img , 然后 tina sdk 中执行pack
,生成的产物tina_d1-nezha_min_uart0.img
中就包含了我们的代码了.然后用全志的工具,将
tina_d1-nezha_min_uart0.img
烧录到哪吒主板上.第一步就完成了.这样就可以正常引导了么? 答案是否定的.
在前面 uboot 的引导指令用的是
bootm 45000000
, bootm 是引导 linux kernel 的,包含了引导协议的一些东西, 我们作为一个裸机程序,我们可以使用 uboot 的go
命令之间跳转到0x45000000
处运行, 将 boot_normal 改为sunxi_flash read 45000000 ${boot_partition};go 45000000
即可, 但是目前 tina 默认的 uboot 没有编译go
指令, 进入lichee/brandy-2.0/u-boot-2018
目录, 执行make menuconfig
, 然后在 Command line interface --> Boot commands 中选中 go 指令,保存后,重新编译, 在打包一次就可以了.tina uboot 默认的环境变量信息在文件
device/config/chips/d1/configs/nezha/env.cfg
里面,可以将 boot_normal 改好后再编译,就不用在 uboot 交互界面中修改环境变量了.上电
bingo!少年, 下一步就开始在哪吒上运行你的 Dreeam OS 吧!
最后的补充注意事项: RISC-V 芯片运行在 SBI 环境, S Mode 下,所以如果裸机程序 M 模式的代码是无法正常运行的.
-
用 Rust 探索 RISC-V 主板 D1 之 GPIO
gpio 是单片机或者单板机和外部硬件沟通的桥梁,通过它可以控制外部硬件,可以建立通讯,可以获取传感器数据等
D1 开发板和树莓派一样,对外引出了 40pin 引脚, 这些引脚包含3.3v,5v供电, GND , 以及几个未使用(NC)引脚, 然后就是我们要讲到的 GPIO 引脚.
辅助利器
开发 gpio 应用离不开几个利器
1. 原理图
全志已经开放原理图下载, 下载地址: https://developer.allwinnertech.com/downloads/resources/24
根据原理图,我们可以看到 40pin 引脚与之对应的芯片端口
这里要说明 D1 板子用 PCF8574 扩展了 8 个 IO 分别是 PP0-PP7 ,其他引出的 IO 来至 D1 这颗芯片, 并且由于 IO 端口不足, 40 Pin 里面物理 32pin 和 38pin 为未启用(NC), 树莓派中是两个 GPIO 端口
2. debugfs
第二个利器就是 debugfs Wiki: Debugfs
debugfs 传承了 Linux 一切皆文件的理念,把内核更多信息通过文件系统展现给开发者,这里当然就包括了我们要的 gpio 信息
debugfs 默认挂载在
/sys/kernel/debug
, 如果没有挂载,可以执行mount -t debugfs none /sys/kernel/debug
挂载在 /sys/kernel/debug 目录下有个 gpio 文件
gpiochip0: GPIOs 0-223, parent: platform/2000000.pinctrl, 2000000.pinctrl: gpio-115 ( |usb1-vbus ) out lo gpio-116 ( |otg_det ) in lo gpio-117 ( |otg_id ) in hi gpio-144 ( |phy-rst ) out hi gpio-166 ( |cd ) in hi IRQ gpio-202 ( |wlan_hostwake ) in hi gpio-204 ( |wlan_regon ) out lo gpio-208 ( |bt_wake ) out lo gpio-209 ( |bt_hostwake ) in hi gpio-210 ( |bt_rst ) out lo gpiochip1: GPIOs 2020-2027, parent: i2c/2-0038, pcf8574, can sleep:
里面内容显示了,板子上有两部分 gpio 组成,第一部分(gpiochip0)有 0-223 ,共224 个 gpio 端口,来自 D1 芯片, 第二部分(gpiochip1) 2020-2027 , 共8个 gpio 端口来自 PCF8574, 并且 8574 是通过 i2c 连接的 , 同时又显示了gpiochip0 中已经做了配置的 GPIO 接口
D1 芯片中的 gpio 信息可以在
/sys/kernel/debug/pinctrl/2000000.pinctrl/pins
找到编号和芯片引脚名称(PA1,PB2等)的对应关系registered pins: 88 pin 32 (PB0) pin 33 (PB1) pin 34 (PB2) pin 35 (PB3) pin 36 (PB4) pin 37 (PB5) pin 38 (PB6) pin 39 (PB7) pin 40 (PB8) pin 41 (PB9) pin 42 (PB10) pin 43 (PB11) pin 44 (PB12) pin 64 (PC0) pin 65 (PC1) pin 66 (PC2) pin 67 (PC3) ...
3. sysfs
sysfs 和 debugfs 一样,通过文件系统,用户不仅可以查看信息,还可以操作硬件.
在
/sys/class/gpio
目录下有四个文件,分别是 export,gpiochip0 ,gpiochip2020,unexport其中 gpiochip0,gpiochip2020 链接的是芯片两个gpio主控
export 和 unexport 用来控制 gpio 的开启与关闭
# 启用 2020 号 gpio 端口, 根据上面的信息,可以知道 2020 对应扩展 IO PP0 , 也就是 40pin 引脚中的 GPIO8 echo 2020 > export # 执行完 echo 2020 > export 后, 会在 /sys/class/gpio 中创建一个目录 /sys/class/gpio/gpio202 , 在这个目录里面就可以设置 gpio 的in 和 out 以及读取或者输出高低电平 cd /sys/class/gpio/gpio2020 # 设置为输出 echo out > direction # 设置高电平 echo 1 > value # 设置低电平 echo 0 > value # 执行代码后, 如果接了 LED 灯, 灯就会亮了又灭了
Rust 在 gpio 方面的支持情况
rust 有以下一些 crate
1. linux-embedded-hal
Implementation of the embedded-hal traits for Linux devices
2. gpio-cdev
基于 GPIO character device ABI 的库
3. sysfs-gpio
基于 sysfs 操作 gpio 的库, 原理如上面手动操作 sysfs 是一样的
4. gpio-utils
操作 gpio 的小工具程序, 基于
sysfs_gpio
Rust Demo (使用
cdev-gpio
)- list gpios
extern crate gpio_cdev; use gpio_cdev::*; fn main() { let chip_iterator = match chips() { Ok(chips) => chips, Err(e) => { println!("Failed to get chip iterator: {:?}", e); return; } }; for chip in chip_iterator { let chip = match chip { Ok(chip) => chip, Err(err) => panic!("Failed to open the chip: {:?}", err) }; println!( "GPIO chip: {}, \"{}\", \"{}\", {} GPIO Lines", chip.path().to_string_lossy(), chip.name(), chip.label(), chip.num_lines() ); for line in chip.lines() { match line.info() { Ok(info) => { let mut flags = vec![]; if info.is_kernel() { flags.push("kernel"); } if info.direction() == LineDirection::Out { flags.push("output"); } if info.is_active_low() { flags.push("active-low"); } if info.is_open_drain() { flags.push("open-drain"); } if info.is_open_source() { flags.push("open-source"); } let usage = if !flags.is_empty() { format!("[{}]", flags.join(" ")) } else { "".to_owned() }; println!( "\tline {lineno:>3}: {name} {consumer} {usage}", lineno = info.line().offset(), name = info.name().unwrap_or("unused"), consumer = info.consumer().unwrap_or("unused"), usage = usage, ); } Err(e) => println!("\tError getting line info: {:?}", e), } } println!(); } }
cdev-gpio 的接口中, 通过 chips() 获取 gpio控制器列表, D1 中的/dev/gpiochip0 和 /dev/gpiochip1
每个 chip 中有 lines 列表,就是控制器下的 gpio 列表 line 就是 gpio 对象, 可以 进行 set_value , get_value ,以及设定输入输出等操作
总体来说, Linux 对 gpio 封装已经很简单, rust 在这方面支持也比较完善. 后续文章还有 i2c, spi , uart 等方面的操作,欢迎关注,欢迎拍砖.
-
[Happy Hacking Nezha Board] 小孩子才做选择,我全都要 BOOT
乘胜追击
前面完成引导 rt-smart 后, 开始继续其他功能的研究.
常规我们使用 raspberry pi 以及其他 Linux 系统的时候, 一般我们的 kernel 等信息都是放在 /boot 目录下的,大多数会格式化成独立的一个分区.
那么我们就照着这个方向去改造哪吒板子, 目前已经初见成效了, 我做了一个 demo 的 image , 供大家测试, 后续 RVBoards 会发布正式的 Debian 版本.
- 下载地址: d1-multi-kernel
说明
镜像前面几个 1-3 分区是全志的预留的几个分区,占用空间很间,这里不去动他.
第 4 分区是 vfat 格式的 boot 分区, kernel 和 dtbo 等文件存放在这里面
demo 镜像 boot 分区文件说明
overlay
: 存放 dtb overlay 文件boot_debian.img
debian 内核boot_tina.img
tina 内核config.txt
引导配置文件rt-smart
rt-smart 执行程序
config.txt 配置说明
配置示例
# mode # 0 boot bare metal bin # 1 boot linux kernel mode=1 bin=rt-smart kernel=boot_debian.img # dtb overlay # load overlay/${dtoverlay}.dtbo dtoverlay=test-overlay # uboot vars # for debian mmc_root=/dev/mmcblk0p6 # for tina #mmc_root=/dev/mmcblk0p5
详细说明
-
mode
:配置引导模式 0 为引导二进制程序 1 为引导linux 内核 (目前这个版本的 内核文件需要使用
mkbootimage
打包生成,就是 tina sdk 中pack
命令生成的boot.img
文化) -
bin
mode=0 的时候引导的文件
-
kernel
mode=1 的时候引导的文件
-
dtoverlay
dtb overlay 配置, 将会加载 overlay/${dtoverlay}.dtbo 这个文件,后续将会支持多个文件加载
-
mmc_root
这个是作为 bootargs 中 root 参数传递给内核的,告诉内核 root 在什么分区,默认是
/dev/mmcblk0p5
, demo 镜像中有多个内核,多个 rootfs ,所以需要配置一下. -
注意
- 配置项"="两边不能有空格
- config.txt 是作为 uboot 的环境变量加载的,可以配置其他变量覆盖 uboot 内部的变量
原理
主要就是利用 uboot 的 fatload 这个指令,从 mmc 中 vfat 文件系统中加载指定文件到内存中使用.
用到相关的指令
fatload
,env import
,fdt
等核心配置
# demo 镜像中的 bootcmd=run boot_check setargs_mmc boot_mmc # 其中 setargs_mmc 是全志默认的,设置 mmc 加载的 bootrags 的指令 # boot_check 检测 mmc 是否启用,然后加载 config.txt 文件,再加载 dtbo 文件 boot_check=run card_init;mmcinfo;mmc part;fatload mmc ${mmc_dev}:${mmc_boot_part} 47000000 config.txt;env import -t 47000000 ${filesize}; test -n "$dtoverlay" && fatload mmc ${mmc_dev}:${mmc_boot_part} 48000000 overlay/${dtoverlay}.dtbo; fdt apply 48000000 # boot_mmc 就是根据 mode 引导不同系统了 boot_mmc=if test ${mode} -eq 0; then fatload mmc ${mmc_dev}:${mmc_boot_part} 45000000 ${bin}; go 45000000; else fatload mmc ${mmc_dev}:${mmc_boot_part} 45000000 ${kernel}; bootm 45000000; fi
原理和实现其实很简单, 后续还可以继续改进,支持多个 dtbo 加载, tftp 加载(方便快速调试) 等等.
后记
完成多系统引导就这么简单了, 后续文章我会再写一个 dtb overlay 的 demo.
注意事项: demo 镜像中, debain 的 rootfs 大小太小,更大空间,需要自行处理一下. -
「RVBoards-哪吒」开启 SSH 和 VNC 远程访问,摆脱烦人的鼠标键盘显示器
单板机,上手比较烦人的就是要准备配套的鼠标键盘以及显示器,通过 SSH 或者 VNC 就可以在自己电脑上远程进行操作,更加方便.
准备材料
- 哪吒开发板 (RVBoards Debian 系统)
- 串口调试线
- 网络已经联通(联网不在这里讨论,可以另外写一篇文章了)
开启 SSH
系统默认配置禁用了 root 远程 ssh 登陆, 如果是普通权限用户没有这个问题.
-
开启 root ssh 远程登陆
编辑 /etc/ssh/sshd_config
将
#PermitRootLogin without-password
修改为PermitRootLogin yes
systemctl restart sshd
重启 ssh 服务即可 -
ssh 访问
使用
ssh [email protected]
就可以登陆访问了,默认密码是rvboards
使用
ssh-copy-id [email protected]
可以设置公钥访问,省掉密码输入
开启 VNC 服务
Linux 上有很多 vnc 服务程序,这里我们选择 tigervnc
- 安装软件
apt update apt install tigervnc-standalone-server -y
-
tigervnc server 常规使用方法
启动服务:
vncserver -localhost no -display :1
上述命令启动 vncserver 并且使用 :1 编号的显示器, :0 默认被启动的 xserver使用了, -localhost no 表示可以远程访问
第一次启用的时候会提示输入密码, 建议使用和 root 一样的密码,便于记忆, 同时可以配置使用 linux 系统认证, 这个哪吒玩家可以自己去查看相关资料.
查看服务:
vncserver -list
TigerVNC server sessions: X DISPLAY # RFB PORT # PROCESS ID SERVER :1 5901 647 Xtigervnc
停止服务
vncserver -kill
vncserver -kill :1 # 结束 :1 display 的 vnc 服务
配置分辨率, 使用
-geometry 1280x800
参数目前哪吒支持的分辨率
1920x1080 60.00 1600x1200 60.00 1680x1050 60.00 1400x1050 60.00 1360x768 60.00 1280x1024 60.00 1280x960 60.00 1280x720 60.00 1024x768 60.00 800x600 60.00 640x480 60.00
-
配置 VNC server 开机启动
开机启动最简单的方式是在 /etc/rc.local 中加入启动脚本,以下是示例
echo "start vnc server" export HOME=/root /usr/bin/vncserver -localhost no -display :1 -geometry 1280x800 echo "vnc server started" # 这里需要先配置 HOME 环境变量, vncserver 需要
-
VNC 远程连接
VNC 有很多客户端, ReadVNC 的 VNC Viewer 推荐一下,并且有 Chrome 的插件, 输入ip和端口号就可以连接了,密码就是初次启动 vncserver 配置的密码
总结
linux 生态下, 远程访问是比较容易的, SBC 级别的设备,大多比较精简,需要自己去安装配置,借此文抛砖引玉,欢迎一起交流.
吐槽一下目前系统层面对 D1 的显示驱动优化的比较差, 性能弱,使用 VNC 操作 gui 大大提升用户体验.