tbot.machine.board
¶
This module contains definitions for interacting with embedded hardware. tbot models hardware in a ‘layered’ approach:
The underlying physical hardware is called a board. The board class defines how tbot can connect to the board’s console, how to turn power on and off, etc.
Ontop of the board, the software ‘layer’ is defined. This might be a U-Boot, a Linux directly, or a Linux ontop of a U-Boot. It could also be something completely custom if you implement the relevant
Initializer
s andShell
s.
Each of these ‘layers’ is its own machine. See the docs for:
Board Hardware¶
The board-machine should inherit from tbot.machine.board.Board
and
define the pysical access for the hardware. This means the connector will most
likely be a ConsoleConnector
and it might
make use of the tbot.machine.board.PowerControl
initializer to turn
on/off power for the board.
Example:
from tbot.machine import connector, board, linux
class MyBoard(
connector.ConsoleConnector,
board.PowerControl,
board.Board,
):
# TODO
- class tbot.machine.board.Board[source]¶
Bases:
RawShell
Base class for board-machines.
This class does nothing special except providing the
.interactive()
method for directly interacting with the serial-console.- ch: Channel¶
Channel to communicate with this machine.
Warning
Please refrain from interacting with the channel directly. Instead, write a
Shell
that wraps around the channel interaction. That way, the state of the channel is only managed in a single place and you won’t have to deal with nasty bugs when multiple parties make assumptions about the state of the channel.
Board Initializers¶
- class tbot.machine.board.PowerControl[source]¶
Bases:
Initializer
Machine-initializer for controlling power for a hardware.
When initializing,
poweron()
is called and when deinitializing,poweroff()
is called.- abstract poweron() None [source]¶
Power-on the hardware.
If the machine is using the
ConsoleConnector
, you can useself.host.exec0()
to run commands on the lab-host.Example:
def poweron(self): self.host.exec0("power-control.sh", "on")
- power_check() bool [source]¶
Check if the board is already on and someone else might be using it.
Implementations of this function should raise an exception in case they detect the board to be on or return
False
. If the board is off and ready to be used, an implementation should returnTrue
.
- powercycle_delay: float = 0¶
Delay between a poweroff and a subsequent poweron.
This delay is only accounted for when both poweroff and poweron happen in the same tbot process. If you are calling tbot multiple times really fast, the delay is not performed!
Using this attribute over, for example, a
time.sleep()
call in thepoweroff()
implementation has the advantage that the delay is not performed at the end of a tbot run, only for powercycles “in the middle”.New in version 0.9.3.
Board Software¶
The ‘software layer’ machines for a board should not directly use a connector
to connect to the boards console, but instead rely on the generic
tbot.machine.board.Connector
class. This allows cleanly separating
the hardware and software and thus reusing the software machines with different
hardware.
In practice, these ‘software machines’ should be defined like this:
from tbot.machine import board
class MyBoard(
...
board.Board,
):
# Hardware machine
...
class MyBoardSoftware(
board.Connector,
...
):
# Software machine
...
And then used like
with MyBoard(lh) as b:
with MyBoardSoftware(b) as bs:
bs.exec(...)
# Or, more consise
with MyBoard(lh) as b, MyBoardSoftware(b) as bs:
bs.exec(...)
Board U-Boot¶
If you are using U-Boot you’ll probably want access to it from your testcases.
This is done using a U-Boot machine. Like any software ‘layer’, a U-Boot
machine should use the tbot.machine.board.Connector
class. The
shell should be a tbot.machine.board.UBootShell
.
In case your U-Boot is configured for autoboot, you’ll want to use the optional
UBootAutobootIntercept
initializer as well.
Example:
class BoardUBoot(
board.Connector,
board.UBootShell,
):
prompt = "U-Boot> "
- class tbot.machine.board.UBootShell[source]¶
Bases:
Shell
,UbootStartup
U-Boot shell.
The interface of this shell was designed to be close to the Linux shell design. This means that U-Boot shells also provide
ub.escape()
- Escape args for the U-Boot shell.ub.exec0()
- Run command and ensure it succeeded.ub.exec()
- Run command and return output and return code.ub.test()
- Run command and return boolean whether it succeeded.ub.env()
- Get/Set environment variables.ub.interactive()
- Start an interactive session for this machine.
There is also the special
ub.boot()
which will boot a payload and return the machine’s channel, for use in a machine for the booted payload.- boot_timeout: float | None = None¶
Maximum time from power-on to U-Boot shell.
If tbot can’t reach the U-Boot shell during this time, an exception will be thrown.
Maximum time from power-on to U-Boot shell.
If tbot can’t reach the U-Boot shell during this time, an exception will be thrown.
- prompt: str | bytes = 'U-Boot> '¶
Prompt which was configured for U-Boot.
Commonly
"U-Boot> "
,"=> "
, or"U-Boot# "
.Warning
Don’t forget the trailing space, if your prompt has one!
- escape(*args: str | Special) str [source]¶
Escape a string so it can be used safely on the U-Boot command-line.
- exec(*args: str | Special) Tuple[int, str] [source]¶
Run a command in U-Boot.
Example:
retcode, output = ub.exec("version") assert retcode == 0
- exec0(*args: str | Special) str [source]¶
Run a command and assert its return code to be 0.
Example:
output = ub.exec0("version") # This will raise an exception! ub.exec0("false")
- Return type:
- Returns:
The command’s console output. It will also contain a trailing newline in most cases.
- test(*args: str | Special) bool [source]¶
Run a command and return a boolean value whether it succeeded.
Example:
if ub.test("true"): tbot.log.message("Is correct")
- Return type:
- Returns:
Boolean representation of commands success.
True
if return code was0
,False
otherwise.
- env(var: str, value: str | Special | None = None) str [source]¶
Get or set an environment variable.
Example:
# Get the value of a var value = ub.env("bootcmd") # Set the value of a var lnx.env("bootargs", "loglevel=7")
- boot(*args: str | Special) Channel [source]¶
Boot a payload from U-Boot.
This method will run the given command and expects it to start booting a payload.
ub.boot()
will then return the channel so a new machine can be built on top of it for the booted payload.Example:
ub.env("bootargs", "loglevel=7") ch = ub.boot("bootm", "0x10000000")
- Return type:
- interactive() None [source]¶
Start an interactive session on this machine.
This method will connect tbot’s stdio to the machine’s channel so you can interactively run commands. This method is used by the
interactive_uboot
testcase.
- property ram_base: int¶
Return the base address of RAM for this U-Boot instance.
This address can be used as a safe bet when your testcase needs to store something in RAM.
Example:
serverip = # ... filepath = # ... ub.exec0("tftp", hex(ub.ram_base), f"{serverip}:{filepath}") ub.exec0("iminfo", hex(ub.ram_base))
- class tbot.machine.board.UBootAutobootIntercept[source]¶
Bases:
Initializer
,UbootStartup
Machine-initializer to intercept U-Boot autobooting.
The default settings for this class should work for most cases, but if a custom autoboot prompt was configured, or a special key sequence is necessary, you will have to adjust this here.
Example:
import re class MyUBoot( board.Connector, board.UBootAutobootIntercept, board.UBootShell, ): autoboot_prompt = tbot.Re("Press DEL 4 times.{0,100}", re.DOTALL) autoboot_keys = "\x7f\x7f\x7f\x7f"
Board Linux¶
The board’s Linux machine can be configured in different ways, depending on your setup and needs.
If you do not need any bootloader interaction, you can define it in a way that goes straight from power-on to waiting for Linux. This is done very similarly to the U-Boot machine above, by using the
board.Connector
.If you do need bootloader interaction (because of manual commands to boot Linux for example), you should instead use the much more powerful
board.LinuxUbootConnector
. This class allows to boot Linux from U-Boot, either by passing it an existing U-Boot machine, or by automatically waiting for U-Boot first and then booting Linux.
In code, the two options look like this:
Example for a standalone Linux (no bootloader interaction):
from tbot.machine import board, linux
class StandaloneLinux(
board.Connector,
board.LinuxBootLogin,
linux.Bash,
):
# No config for the connector needed
# LinuxBootLogin handles waiting for Linux to boot & logging in
username = "root"
password = "hunter2"
Example for a Linux booting from U-Boot:
from tbot.machine import board, linux
class MyUBoot(board.Connector, board.UBootShell):
...
class LinuxFromUBoot(
board.LinuxUbootConnector,
board.LinuxBootLogin,
linux.Bash,
):
# Configuration for LinuxUbootConnector
uboot = MyUBoot # <- Our UBoot machine
def do_boot(self, ub): # <- Procedure to boot Linux
ub.env("autoload", "false")
ub.exec0("dhcp")
return ub.boot("run", "nfsboot")
# LinuxBootLogin handles waiting for Linux to boot & logging in
username = "root"
password = "hunter2"
With both options, you’ll use the
LinuxBootLogin
initializer to handle the boot
and login.
- class tbot.machine.board.LinuxUbootConnector(b: Board | UBootShell)[source]¶
Bases:
Connector
,LinuxBootLogin
,BoardMachineBase
Connector for booting Linux from U-Boot.
This connector can either boot from a
Board
instance or from aUBootShell
instance. If booting directly from the board, it will first initialize a U-Boot machine and then use it to kick off the boot to Linux. See above for an example.- abstract property uboot: Type[UBootShell]¶
U-Boot machine to use when booting directly from a
Board
instance.
- do_boot(ub: UBootShell) Channel [source]¶
Boot procedure.
An implementation of this method should use the U-Boot machine given as
ub
to kick off the Linux boot. It should return the channel to the now booting Linux. This will in almost all cases be achieved by using thetbot.machine.board.UBootShell.boot()
method.Example:
from tbot.machine import board, linux class LinuxFromUBoot( board.LinuxUbootConnector, board.LinuxBootLogin, linux.Bash, ): uboot = MyUBoot # <- Our UBoot machine def do_boot(self, ub): # <- Procedure to boot Linux # Any logic necessary to prepare for boot ub.env("autoload", "false") ub.exec0("dhcp") # Return the channel using ub.boot() return ub.boot("run", "nfsboot") ...
- classmethod from_context(ctx: Context) Iterator[Self] [source]¶
Create this machine from a tbot context.
This method defines how tbot can automatically attempt creating this machine from a given context. It is usually defined by the connector but might be overridden by board config in certain more complex scenarios.
This method must return a context-manager that, upon entering, yields a fully initialized machine. In practical terms this means, the implementation must enter the “machine’s context” as well. As an example, the most basic implementation would look like this:
@contextlib.contextmanager def from_context(cls, ctx): # Create instance and enter its context, in this example, no # args are passed to the constructor ... with cls() as m: yield m
- class tbot.machine.board.LinuxBootLogin[source]¶
Bases:
Initializer
,LinuxBoot
Machine
Initializer
to wait for linux boot-up and automatically login.Use this initializer whenever you have a serial-console for a Linux system.
Example:
from tbot.machine import board, linux class StandaloneLinux( board.Connector, board.LinuxBootLogin, linux.Bash, ): # board.LinuxBootLogin config: username = "root" password = "hunter2"
- login_prompt = 'login: '¶
Prompt that indicates tbot should send the username.
- login_delay = 0¶
The delay between first occurrence of login_prompt and actual login.
This delay might be necessary if your system clutters the login prompt with log-messages during the first few seconds after boot.
- password_prompt: bytes | BoundedPattern | Pattern | str = 'assword: '¶
Prompt that indicates tbot should send the password.
- boot_timeout: float | None = None¶
Maximum time for Linux to reach the login prompt.
The timer starts after initiation of the boot. This may either be power-on if booting into Linux directly or the point where the boot process is initiated from the bootloader (when using
LinuxUbootConnector
).New in version 0.10.0.
- class tbot.machine.board.AskfirstInitializer[source]¶
Bases:
Initializer
,LinuxBoot
Initializer to deal with
askfirst
TTYs.On some boards, the console is configured with
askfirst
which means that the getty for logging in is only spawned after an initial ENTER is sent. This initializer takes care of that by first waiting for theaskfirst
prompt and then sending ENTER.The
askfirst
prompt can be customized with theaskfirst_prompt
attribute.Example:
from tbot.machine import board, linux class StandaloneLinux( board.Connector, board.AskfirstInitializer, board.LinuxBootLogin, linux.Bash, ): # board.LinuxBootLogin config: username = "root" password = "hunter2"
New in version 0.10.6.
- askfirst_prompt = 'Please press Enter to activate this console.'¶
Prompt that indicates the board is waiting for
askfirst
confirmation.