Hi, I'm Sean. Today I'm going to talk about semi-hosting in the context of Uboot and what
it is and how it works and maybe why you might want to use it. So, first I want to ask how
do you boot Strapasystem? So, you might do this for two reasons. One, you have a new
board right from the factory and it has nothing on it at all and you have to get something
on it and the other one is maybe you bricked it and this happens to me sometimes. It actually
happens quite a lot especially when I'm working on Uboot and the board will no longer boot.
So, there's two basic steps usually. The first one is you want to get something running on your
board and the second one is you want to then write something to storage so you don't have to do
the process again. So, there's a variety of protocols you can use. USB of course. I like
UMS. It's very nice. It makes your device look like a USB flash drive which is very, very convenient.
There's also a bunch of Ethernet stuff. The classic TFTP. Baskoot makes an appearance twice
because it can do both. If you have an SD card, bootstrapping is super easy. You just pop out
the SD card and put whatever you want on it and put the SD card back in but a lot of boards don't
have SD cards. So, this is not always an option. There's Serial. This is usually kind of slow
so you might only want to use it for the code execution part but it's definitely there.
Some boards have it built into the boot loader. You can just flash something over Serial.
And there's also JTAG and JTAG is kind of a classic one. Also slow. You probably wouldn't
want to flash your whole root file system over it but it's pretty reliable and a lot of boards have it.
What if you only have JTAG and you don't have any of these other nice protocols?
So, I'd like to take a little bit of a different approach to the problem
and let's talk about something totally different which is the NXP Core IQ line of
communications processors. These are the newest iterations of a very old line
which stretches to the M68K and there's a very long lineage of PowerPC stuff in there
and they tend to have lots of Ethernet, some PCIe, some USB but not any display interfaces. So,
they're not really media sucks and they often have hardware accelerated networking
so you can do some stuff in hardware which you would normally do in software.
And this is kind of the main selling point on why you might want to use these.
So, all of these have something they call a reset configuration word or RCW
and this started back in the PowerPC days as just basic initialization. What enviousness
your sock is going to be, maybe what dividers you're going to have on some clocks, how wide
your boot bus is, what are you going to do with your debug pins and this is kind of a small amount
of data so they stuck it on there some pull-ups and pull-downs on some of the pins and this is a
very standard thing you'll see on a lot of different socks and then they wanted some pin
boxing because when they originally started with this they all the pins were fixed function
and you can sell more chips if you can change the function of some of the pins so that you can use
like USB on one chip and maybe ethernet on another so they added some pin boxing and they added it
to the RCW and then they added a lot more pin boxing because the more pin boxing you have the
more applications your chip can fit into and so they started running out of pins because they
started getting maybe like 128, 256, 512 bits of stuff that they needed to configure and so they
decided they were going to put the RCW on the boot device so the first thing the sock does when it
boots up is it reads off this RCW and it configures all the pins and then it continues with the boot
and this is kind of convenient but it creates a chicken and egg problem where in order for your
sock to boot up there has to be something on your initial device and if you're in a situation where
you have to bootstrap it there's nothing there so the sock won't boot up so what they did is they
created a hard-coded reset configuration word this is for maximum compatibility they would disable
all the peripherals and you would just have your boot device and so you could always boot into this
and be safe and not break your board but this is not so great because they never added runtime pin
muxing so this chip you select a function for your pins and you can't change it there are a few
pins where you can change it but for the most of them you're stuck so when you have this maximum
compatibility RCW with everything disabled you have no ethernet you have no usb you have no serial
even and all you get is jtag and your boot device so nxp knew they had a problem and they decided to
solve it by introducing this override so you would boot via the hard-coded reset configuration word
and then you would program via jtag the values that you actually wanted that would enable all your
peripherals for your board and then you would do a partial reset and it would come up and it would
load everything like it was supposed to but there's a couple problems with this the main one is that
they never documented this stuff so in order to use it you you have to use the jtag probe which is
like most jtag probes kind of a gouge because they they know you're buying the chip so you
gotta have the jtag probe and you have to use their IDE which is a yearly subscription and they're
not cheap so this is not a great situation and if you didn't think this was great here's a glowing
review i found on the forums our manufacturer uses a single pc to perform the initial programming
on this pc they have an evaluation copy of code warrior which is their IDE every time that evaluation
copy expires they erase the hard drive of the pc install the os again and load another evaluation
copy uh so this is not ideal uh and i thought about how i might address this uh and make it better
and i remembered um something that i learned about a couple months ago it's called semi hosting
and the basic idea of semi hosting is that you attach a debugger in my case it's over jtag
and uh your code is going to execute a special breakpoint instruction and when your debugger
sees this it will read out there on opcode in r0 and an argument in r1 and it will do something for
you and then it will give you a return code back in r0 and this is very very similar to how sys calls
work because your program will execute a special instruction the operating system will read out
your registers it will do something for you and give you a return code so what do you get well
the thing that i wanted most is serial because i didn't have any so first i looked at some of the
sys write c and sys write c is basically put char uh you uh so we can implement put s here
and so we're going to take in a string and we're going to loop over all the characters in the
string and for each character we're going to trap or execute our breakpoint instruction
and we're going to pass for our opcode the write c and we're also going to pass a pointer to the
character uh and if you may know that put char actually just takes the character um and so this
is kind of an unfortunate uh performance implication because we have one breakpoint
and one memory access per character in the string and for j tag this is not very performant
if you've ever used a 300 bod modem you know that's very slow this is even slower so this is
really not useful if you actually want to use your serial output so we can do better though
they also have something called sys write zero this is basically put s
so uh our pit of implementation gets very simple uh we're just going to trap with write zero
and now we get one breakpoint per string uh but we still have to do one memory access per character
and the problem is that we don't want to read off the end of the string we have to make sure
that we don't go past the null terminator so the debugger has to read a character
and then see it was at the null terminator and if it's not you read another character
and you keep doing this uh and we really don't want to go off the end uh but the problem is that
for j tag setting up a read is a pretty intensive process um there's a lot of overhead and it can
be still pretty slow so this is faster uh about 10 times as fast but it's still slow uh really
not usable but we can do even better um so we're going to use sys write which is basically the
write system call and for this one because we have multiple parameters uh the previous ones
only had one parameter so it just goes in the argument but for this one we're going to fill
in our arguments inside of a struct and we're going to take the file descriptor and the buffer
and the length of the buffer and we're going to fill this in with standard out and uh there are
string and the length of our string and then we're going to trap and we're going to pass a pointer
to our struct and this is generally how we pass multiple arguments um to semi-hosting because
there's only one argument register so they will take a pointer to the struct and so now we get
one breakpoint per string and two memory accesses per string and this is reasonably fast we can do
stuff with this and it's not glacially slow um so this is the kind of implementation I ended up
using it uh and if you've been paying attention you'll note that sys write kind of implies the
existence of sys open and you can open any file on your host system which is pretty convenient
and you can do all the standard stuff like seeking it and reading it
and closing it uh we don't get stat but we do get the file length which is mostly what we want
because usually we just want to open it find out how long it is and then read the whole thing
uh so in uboot you may classically do something like this if you want to load your linux and
then boot it you're going to load it from mmc0 add a particular address uh and then you're
going to give your file name and then you'll boot it uh and so we can replace this with load host fs
which is the something on your host debugger file system uh and that linux image will get
read from the directory that you're running your debugger from um and it it's the same structure
because under the hood it's using the same api and there's a dash because there's only one host fs
and we don't need to have multiple debugger support uh and there's a special file called
colon tt uh which i think stands for teletype and this is your standard in and standard out
and almost everybody uses this uh except q mu because q mu doesn't have this huge overhead
for memory accesses so they don't actually care uh if you can use your console with uh read and write
and so you just use uh write zero with them and it's works uh so one classic problem with
booting with j tag is that your regular boot process is going to look something like load spl
and spl is going to initialize d ram and then spl is going to load regular u boot into d ram
and execute it and when you do this with j tag instead you have to load spl over j tag and j
tag is going to run and initialize d ram and sometime you have to uh load u boot into j you
load u boot into d ram over j tag but we don't really know when um and so a really classic way
to do this is uh you just pick a time and you wait that long and then you load u boot but this
is kind of awful because if you have any kind of variance in how long d ram initialization takes or
how long it takes especially if you're doing other hardware initialization um you have to
just wait a lot longer and in the average case you're going to be doing nothing and this can
really drive you nuts as a developer because you might be waiting like 20 seconds because
sometimes it takes 20 seconds but most of the time it doesn't um so you can also reimplement
d ram in tickle and uh this is a really common thing for vendors to do because they love just
you know it's it's very simple for them they just write all the registers and it happens over j tag
and this avoids the whole timing problem because we know exactly when d ram has vision is sliced
but it's a totally different process from normal you have to specify your parameters in a different
format in a different language it's not going to be tested as much and it probably won't initialize
things in the same way so it's it can be more buggy um and it's kind of uh worrisome especially
when you have to uh your regular u boot will work fine and maybe this doesn't work so well
but semi hosting makes this really simple because spl can load and then it will over j tag
and initialize d ram and it says to your host please load u boot at this address
and your host will do that and then it continues on its way and it's um extremely simple to use
and it solves this whole timing problem which can be very annoying uh so what else do you get
well we get some error handling uh error no is practically essential um to find out why
something failed uh is error is not uh the idea of is errors that you will pass in a return code
and is there will tell you if it's an error or not but the problem is that some of these semi
hosting commands um have different semantics for the return code and most of the time the
semantic is negative numbers are errors so effectively you're doing this whole big semi
hosting call just to compare to zero um so i don't really know why this is in here and there's
actually several functions that are kind of like that um for example sys uh sys time will get you
the real time which can be helpful if you're uh if your device doesn't have an rtc or you don't
want to initialize it um but sys elapsed will get the number of ticks that your program has been
running so maybe you would use this for uh benchmarking but the overhead of doing semi hosting is
a lot larger than the the amount of precision that you're going to get so i'm not really sure
why you use that one either um there's some libc emulation uh you can pass in a command line you
but we don't really need this because we have the environment and we have the device tree and those
are kind of classic ways to pass in um parameters but if you're not using uboot and you don't have
this sort of system set up uh you can get command line parameters pretty easily there's also a sys
heap which is where you tell the device where it thinks the heap is and where it should malloc stuff
but usually you know this when you compile you say this address range is going to be or i'm going
to stick my heap so also i'm not really sure why that's in there um and as you may have noticed uh
you can write files um so of course you can mess things up especially on unix where you can open
up a lot of files that aren't really files and do some fun stuff with them but you can also just
run arbitrary commands and you can remove files too um so you have to really trust this stuff that
you're going to run uh because as far as i know no one does sandboxing they just implement all this
stuff uh so maybe they shouldn't but that's how it is uh so if you've ever used semi-hosting before
you may be familiar with the this problem uh break points are actually invalid instructions
and your program will crash unless there is a debugger attached and the debugger will handle it for
you and you won't end up executing it um so typically you would have to have two programs one
with semi-hosting enabled and one with semi-hosting not enabled and the one with semi-hosting enabled
you'd have to run with a debugger but we can get around this using a pretty simple trick um this
one is from uh tom verbuer uh and the idea is that in your synchronous support handler
you first check to make sure that we have an invalid instruction and otherwise you panic which
you know probably involves printing out the registers or doing something um complaining loudly on
the serial which you might not have uh then you would do you we need to check to make sure our
instruction which is held in elr is the semi-hosting arm 64 halt instruction which is the special
breakpoint um and the lower bits of the pc are actually not the pc on uh arm because they have
stuff like are you in thumb mode or not um so we need to mask those off well you could probably
just do and uh till the three um and if we actually find out that it was supposed to be a semi-hosting
instruction we're going to disable semi-hosting which on your processor can do whatever it wants
but on u boot it just sits a global variable that says we don't have semi-hosting don't try it again
and then we pretend that we get a failure negative one is almost always a failure
and then we advance the pc by four bytes
so if you want to use semi-hosting in u boot uh you can enable these configs uh the first one
enables semi-hosting of any kind um and also enables this uh command uh the second one semi-hosting
serial will get you some serial input and output and you'll probably want this serial put s uh
because normally u boot will print a character at a time uh and put s will group those characters
into strings and print them all at once and if you want to have this thing you will need to enable
config semi-hosting fallback and if you want to use an spl then you can enable the spl versions
there's no serial version because u boot always enables the uh serial device in spl that it's
using in the regular u boot um and these are the things that i worked on adding uh and i also worked
on config semi-hosting a lot but uh the basic support was already there uh there's also risk
five support from a kautok console and this is pretty recently added so it's either in the january
release or maybe the march release i'm not sure um and if you want to know more about how to enable
this we have a documentation link and of course you're also going to need a debugger so i like to
use open ocd um maybe because i'm a masochist uh and open ocd is a debug server for j tag so the
idea is you launch open ocd and it connects to your debug probe and then you can you can tell the
debug probe to do things like uh start or stop your processor and you can also attach gdb to it
like it's a running process so this is pretty simple for open ocd you just halt the processor
you enable semi-hosting and then you resume it and typically what you would do is in between this
enabling semi-hosting and resuming you would load your program and then resume out a particular
address and this you could stick in a script and just run and automate the whole thing uh so there's
a couple of downsides to open ocd uh you can kind of think of this as like a wish list or
things that are knowing me but not enough that i fixed them uh the one of them is that uses the
same terminal for regular like logging messages like uh you know i attached a debugger um and that
sort of thing as semi-hosting output so they can be kind of get intermixed so you have to watch
out for that uh the serial is cooked which means that when you type something uh nothing happens
until you hit enter and then everything happens uh and this can is kind of okay because if you're
editing a command line um it it's generally really slow if like you hit backspace and then you have
to go to u boot and u boot interprets the backspace and echoes it back and then it gets displayed on
your terminal so cooked is nice here um the problem is that open ocd is single threaded so while it's
waiting for you to input it's not doing anything so if you unplug the device or you hit control c
in your debugger it won't notice until you hit enter uh so this is uh can be kind of fun especially
because even if you know about it you might forget um and this single threaded thing also ties into
there's no sandboxing so ideally you would do something like fork off another process and
maybe unshare some stuff or put it in a ch route and then that would be where you would run all your
semi-hosting stuff like it would open the file and you could limit it to just a few files but
there's no sandboxing so uh your whole system is there uh once again you have to trust your stuff
so should you use semi-hosting uh i would say not unless you have to especially not the serial
stuff but it's good to have if you have to use it it's nice uh and sometimes it's convenient if
you're doing emulation it can be really simple because you don't have to emulate an mmc device
you don't have to write a driver for an mmc device you just uh call your semi-hosting instruction
and you can load the file right into where you want it um and you don't have to do any hardware
and if you're already using jtag boot this can be really nice to solve some of your sequencing
stuff uh but i wouldn't recommend it in general um so i'd like to thank a couple people uh tom
verberware wrote a blog post on this stuff that got me thinking about the whole thing uh andre
psivara uh did the initial semi-hosting and uh i'm he also worked with me when i was upstreaming
my stuff so i'm grateful for that and of course tom rini and simon glass who reviewed and merged
all of this code and a lot of other patches along the years um and of course maric who put me up to
this talk and seiko who employed me while i was writing the code and if you're interested in this
there's that blog post i was talking about uh there is the risk five software spec which is
just the arm software spec but they use a different instruction and different registers and of course
the arm software spec and this link may die because arm has a tendency to rearrange things but uh for
now it works thank you
anyone have a question questions
yeah i do um can you actually use semi-hosting for serial control in linux uh yes but only
for debug prints and i haven't looked into it that closely um i think the whole uh stopping
linux to do a breakpoint is kind of invasive because linux tends not to like that because like
your interrupts for that core will just not happen while it's stuck on the debugger and uh you can
kind of break your devices that expect there to be an interrupt that gets handled in a reasonable
manner um so typically when you stop the processor in linux uh like your emmc will just break um so
generally i've only seen it for debug prints and usually only if like you can't get to the real
serial console yeah okay since we have a couple minutes i have a one more slide
um so normally when you boot print something uh this is what it gets it'll get like hello uh
slash n and it'll normally print this like h e l l o slash r slash n and it inserts the slash r
and it'll do it one character at a time but as we've established earlier this is glitchily slow
on semi-hosted hardware so what i initially did was this and i printed out hello slash n and then
i added the dash slash r um but this will actually break things because they expect it to be r n and
not n r even though like functionally they're the same um so i ended up having to do it the other
way uh so if you're implement this stuff be aware of that although uh if if you are doing this like
on a microcontroller you can probably just put hello r n in your strings and maybe that's better