FreeBSD, UART and Raspberry Pi 3 B+
If you are reading this because you, like me were lost and frustrated… I feel your pain. But let me try and help!
A few weeks ago I added a POE HAT to a Raspberry Pi 3B+ and installed FreeBSD to so I could build a remote access server. I also bough another Raspberry Pi 3B+ to replace by OrangePi Zero + USB uBlox 5 NTP server.
Unlike Raspbian it is surprisingly hard to stop FreeBSD’s kernel from grabbing uart0 for it’s console. Lets start with why you are probably here… how do I stop the kernel from doing this?
echo 'hw.fdt.console="NO"' >> /boot/loader.conf.local
Easy right? It took me more than 24 hours to find this. Most of which was one long Saturday. I could not find much (any) documentation on this parameter. So how did I find it?
One of the embedded device devs from FreeBSD recommended to remove /chosen/stdout-path from the fdt tree. This did not work. After a few hours of messing around with building kernels without baked in uart support and other dead-ends I went back to looking at the fdt stuff.
/usr/src/sys/gnu/dts/arm/bcm2837-rpi-3-b-plus.dts
/usr/src/sys/gnu/dts/arm/bcm2835-rpi.dtsi
/usr/src/sys/gnu/dts/arm/bcm2837.dts
These dtsi and dts files are what eventually end up in the fdt… bcm2837-rpi-3-b-plus.dts holds the /chosen/stdout-path for the rPI 3B+.
chosen {
/* 8250 auxiliary UART instead of pl011 */
stdout-path = "serial1:115200n8";
};
Older version had this set to serial0 but I switched to FreeBSD 12-APHA2 before my long day of experimentation. This one lists serial1 as the default path… which is odd given bluetooth is still disabled so that the hardware UART is attached to the TX and RX GPIO pins. (That is serial0)
So with the default or with a empty (or missing stdout-path) we still somehow end up using serial0. After about an hour of grokking the source I came to these files.
/usr/src/sys/dev/uart/uart_bus_fdt.c
/usr/src/sys/dev/uart/uart_cpu_fdt.c
Both files deal with setting up the uart to have the kernel’s console attached. The function uart_cpu_fdt_probe is the interesting one here.
/* Has the user forced a specific device node? */
cp = kern_getenv("hw.fdt.console");
if (cp == NULL) {
/*
* Retrieve /chosen/std{in,out}.
*/
node = -1;
if ((chosen = OF_finddevice("/chosen")) != -1) {
for (name = propnames; *name != NULL; name++) {
if (phandle_chosen_propdev(chosen, *name,
&node) == 0)
break;
}
}
if (chosen == -1 || *name == NULL)
node = OF_finddevice("serial0"); /* Last ditch */
} else {
node = OF_finddevice(cp);
}
if (node == -1)
return (ENXIO);
Above I have highlighted the most interesting bit.
- hw.fdt.console checked
- if hw.fdt.console is not set, /chosen/std{in,out,out-path} are checked
- if the config in /chosen/std* was not valid a last ditch effort is done to configure serial0
If hw.fdt.console is set to an invalid path e.g. NO no console will be configured! So this is the solution to disabling the kernel from grabbing uart0! We did it reddit
Now that I knew what to do I managed to find this change D3559. A handy note was left from the developer:
The user could disable the fdt console with this by setting it to an invalid node, e.g. hw.fdt.console=“none”
I could not find any other documentation about this parameter so it may stop working in the future. Time will tell.
EDIT: It looks like the internal UART can only run at 115200… I am looking into this as 9600 is what I need for the GPS HAT. I did have the RTC and PPS working without anything fancy aside from adding the dtoverlays and loading the drivers. So that is a small win.
I have also attached a u-boot binary with silent=1 and nulldev configured for people who do not want to recompile u-boot themselves.
UPDATE: the UART can indeed only run at 115200 and FreeBSD’s PL011 driver does not support changing the speed unless running on x86! However u-boot can! Adding the following line to config.txt pins the UART’s baudrate at 9600
init_baudrate="9600"