mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-03-06 02:06:08 +00:00
feat(prodtest): refactor and improve prodtest
This commit is contained in:
parent
2a19b35f3e
commit
2eb1e5b3ca
@ -76,8 +76,10 @@ SOURCE_MOD += [
|
||||
'embed/util/image/image.c',
|
||||
'embed/util/rsod/rsod.c',
|
||||
'embed/util/scm_revision/scm_revision.c',
|
||||
'embed/rtl/cli.c',
|
||||
'embed/rtl/error_handling.c',
|
||||
'embed/rtl/mini_printf.c',
|
||||
'embed/rtl/strutils.c',
|
||||
'vendor/micropython/lib/uzlib/adler32.c',
|
||||
'vendor/micropython/lib/uzlib/crc32.c',
|
||||
'vendor/micropython/lib/uzlib/tinflate.c',
|
||||
@ -98,12 +100,26 @@ FEATURES_AVAILABLE = models.configure_board(TREZOR_MODEL, HW_REVISION, FEATURES_
|
||||
SOURCE_PRODTEST = [
|
||||
'embed/projects/prodtest/header.S',
|
||||
'embed/projects/prodtest/main.c',
|
||||
'embed/projects/prodtest/prodtest_common.c',
|
||||
]
|
||||
|
||||
if 'optiga' in FEATURES_AVAILABLE:
|
||||
SOURCE_PRODTEST += [
|
||||
'embed/projects/prodtest/optiga_prodtest.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_boardloader.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_bootloader.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_button.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_display.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_prodtest.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_get_cpuid.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_haptic.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_help.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_optiga.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_otp_batch.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_otp_variant.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_ping.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_pmic.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_powerctl.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_reboot.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_rgbled.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_sdcard.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_sbu.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_touch.c',
|
||||
'embed/projects/prodtest/cmd/prodtest_wpc.c',
|
||||
]
|
||||
|
||||
env.Replace(
|
||||
|
@ -105,7 +105,7 @@ bool haptic_init(void) {
|
||||
haptic_driver_t *driver = &g_haptic_driver;
|
||||
|
||||
if (driver->initialized) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
memset(driver, 0, sizeof(haptic_driver_t));
|
||||
|
@ -0,0 +1 @@
|
||||
Completely redesigned. See the updated documentation for details.
|
@ -1,448 +1,547 @@
|
||||
# Production Test Firmware
|
||||
This document outlines the protocol used during production for testing and the initial provisioning of Trezor devices.
|
||||
This document outlines the production test firmware (prodtest) and its protocol.
|
||||
|
||||
## Commands and Responses
|
||||
In the production environment, the test equipment sends single-line text commands.
|
||||
These commands start with the command name and can optionally be followed by parameters separated by spaces.
|
||||
The prodtest serves two primary purposes:
|
||||
|
||||
* Testing and initial provisioning of Trezor devices during production.
|
||||
* Device bring-up and testing during development.
|
||||
|
||||
## Command Line Interface
|
||||
|
||||
The prodtest includes a simple text-based command-line interface that can be controlled either by automatic test equipment in a production environment or manually via a terminal application.
|
||||
|
||||
Pressing the ENTER key twice switches the interface to interactive mode, enabling basic line editing, autocomplete functionality (using the TAB key), and text coloring.
|
||||
|
||||
### Commands
|
||||
These commands begin with the command name and may optionally include parameters separated by spaces.
|
||||
|
||||
Command Format:
|
||||
|
||||
`COMMAND [INARG1 [INARG2 [INARG3 ...]]]`
|
||||
`command <arg1> <arg2> ...`
|
||||
|
||||
Example:
|
||||
```
|
||||
CPUID READ
|
||||
haptic-test 500
|
||||
```
|
||||
|
||||
The Trezor device responds with single-line text responses that start with either `OK` or `ERROR`, followed by output values separated by spaces.
|
||||
If the device receives an unrecognized command, it responds with the text `UNKNOWN`.
|
||||
To retrieve a list of all available commands, type `help`.
|
||||
|
||||
Device responds with single line text response starting with words `OK` or `ERROR` optionally followed by output values delimited by spaces.
|
||||
In case of unrecognized command, device responds with text `UNKNOWN`.
|
||||
### Final Responses
|
||||
The Trezor device responds to all commands with single-line text responses, which start with either `OK` or `ERROR`.
|
||||
|
||||
Response Format:
|
||||
|
||||
`OK [OUTARG1 [OUTARG2 [OUTARG3 ...]]]`
|
||||
An `OK` response may include additional parameters separated by spaces. The number and type of parameters depend on the specific command.
|
||||
|
||||
Example:
|
||||
```
|
||||
OK 2F0079001951354861125762
|
||||
OK 2.1.7
|
||||
```
|
||||
|
||||
An `ERROR` response includes an error code and an optional error description enclosed in quotation marks.
|
||||
|
||||
Example:
|
||||
```
|
||||
ERROR invalid-arg "Expecting x-coordinate."
|
||||
```
|
||||
|
||||
#### Predefined Error Codes:
|
||||
|
||||
While commands may define custom error codes as needed, the following are predefined:
|
||||
|
||||
* `error` - Unspecified error; additional details may be provided in the error description.
|
||||
* `invalid-cmd` - The command name is invalid.
|
||||
* `invalid-arg` - Invalid command argument.
|
||||
* `abort` - Operation aborted by the user (e.g., pressing CTRL+C).
|
||||
* `timeout` - The command execution timed out.
|
||||
|
||||
### Progress response
|
||||
|
||||
When a command needs to send structured data that does not fit within `OK` messages, it can use the `PROGRESS` response. A progress response line starts with `PROGRESS`, followed by any number of parameters separated by spaces.
|
||||
|
||||
Example:
|
||||
```
|
||||
PROGRESS move 107 164 24824185
|
||||
PROGRESS move 107 170 24824195
|
||||
PROGRESS move 107 176 24824204
|
||||
PROGRESS move 107 180 24824213
|
||||
PROGRESS move 107 184 24824222
|
||||
```
|
||||
|
||||
### Debug Messages
|
||||
|
||||
In addition to lines beginning with `OK`, `ERROR`, or `PROGRESS`, the prodtest firmware may issue lines starting with `#`. These lines contain debug or informational traces and should be ignored by automation scripts in a production environment.
|
||||
|
||||
Example:
|
||||
```
|
||||
rgbled-set 0 255 0
|
||||
# Setting the RGB LED color to [0, 255, 0]...
|
||||
OK
|
||||
```
|
||||
|
||||
## List of commands
|
||||
|
||||
### PING
|
||||
The `PING` command serves as a no-operation request, and the device responds with `OK` to acknowledge receipt.
|
||||
### help
|
||||
Displays a list of available commands. The command optionally accepts a prefix to filter and display only commands starting with the specified characters.
|
||||
|
||||
Example:
|
||||
```
|
||||
PING
|
||||
help otp
|
||||
# Available commands (filtered):
|
||||
# otp-batch-write - Write the device batch info into OTP memory
|
||||
# otp-batch-read - Read the device batch info from OTP memory
|
||||
# otp-variant-write - Write the device variant info into OTP memory
|
||||
# otp-variant-read - Read the device variant info from OTP memory
|
||||
OK
|
||||
```
|
||||
|
||||
### CPUID READ
|
||||
The `CPUID READ` command reads a 96-bit long unique ID stored in the device's CPU.
|
||||
The command always returns `OK` followed by a 24-digit hexadecimal value representing the unique ID.
|
||||
### ping
|
||||
The `ping` command serves as a no-operation request, and the device responds with `OK` to acknowledge receipt.
|
||||
|
||||
Example:
|
||||
```
|
||||
CPUID READ
|
||||
OK 2F0079001951354861125762
|
||||
```
|
||||
|
||||
### BORDER
|
||||
The `BORDER` command draws a single white border around the screen on a black background. This command has no input parameters and always returns `OK`.
|
||||
|
||||
Example:
|
||||
```
|
||||
BORDER
|
||||
ping
|
||||
OK
|
||||
```
|
||||
|
||||
### DISP
|
||||
The `DISP` command draws vertical color bars on the screen based on a list of specified colors provided as a parameter.
|
||||
Each character in the parameter represents one vertical bar and its color (R - red, B - blue, W - white, any other character - black).
|
||||
The number of characters corresponds to the number of bars.
|
||||
|
||||
Note: On monochromatic displays R, G, B characters are interpreted as the white color.
|
||||
|
||||
Example (to draw 6 vertical bars - red, black, green, black, blue and black):
|
||||
```
|
||||
DISP RxGxB
|
||||
```
|
||||
|
||||
### BUTTON
|
||||
The `BUTTON` command tests the functionality of the device's buttons.
|
||||
It waits for the user to press and release a specified button in a designated timeout period.
|
||||
|
||||
The command required two input parameters:
|
||||
* The first parameter specifies the expected button or combination of buttons, with possible values: LEFT, RIGHT, BOTH.
|
||||
* The seconds parameter specifies the timeout duration in seconds in range 1 to 9
|
||||
|
||||
If the specified button or button combination is not detected within the timeout, the command will return and `ERROR TIMEOUT`.
|
||||
|
||||
Example (to wait for 9 seconds for the left button):
|
||||
```
|
||||
BUTTON LEFT 9
|
||||
OK
|
||||
```
|
||||
|
||||
### TOUCH
|
||||
The `TOUCH` command tests the functionality of the display's touch screen.
|
||||
It draws a filled rectangle in one of the four display quadrants and waits for user interaction.
|
||||
|
||||
The command requires two input parameters:
|
||||
|
||||
* The first parameter, which should be a value between 0 and 3, determines the quadrant where the rectangle will be drawn.
|
||||
* The second parameter, a value between 1 and 9, represents the timeout in seconds.
|
||||
|
||||
If the display is not touched within the specified timeout, the command will return an `ERROR TIMEOUT`.
|
||||
|
||||
The command does not check whether the touch point lies within the quadrant or not. It only returns the x and y coordinate of the touch point.
|
||||
|
||||
Example (to draw a rectangle in the top-left quadrant and wait for 9 seconds for touch input):
|
||||
```
|
||||
TOUCH 09
|
||||
OK 50 90
|
||||
```
|
||||
|
||||
|
||||
### TOUCH_CUSTOM
|
||||
The `TOUCH_CUSTOM` command tests the functionality of the display's touch screen.
|
||||
It draws a filled rectangle on custom coordinates and waits for user interaction.
|
||||
|
||||
The command requires five input parameters:
|
||||
|
||||
* X position of the top-left corner of the rectangle
|
||||
* Y position of the top-left corner of the rectangle
|
||||
* Width of the rectangle
|
||||
* Height of the rectangle
|
||||
* The timeout in seconds
|
||||
|
||||
If the display is not touched within the specified timeout, the command will return an `ERROR TIMEOUT`.
|
||||
|
||||
The device report touch events, coordinates and timestamps (in ms), correctness of the touch point is not checked and is left to the test equipment.
|
||||
|
||||
The test ends with first lift-up event.
|
||||
|
||||
Example (to draw a 100x100 rectangle in the top-left (10,10) position and wait for 15 seconds for touch input):
|
||||
```
|
||||
TOUCH_CUSTOM 10 10 100 100 15
|
||||
TOUCH D 69 35 357300
|
||||
TOUCH U 69 35 357328
|
||||
OK
|
||||
```
|
||||
|
||||
### TOUCH_IDLE
|
||||
The `TOUCH_IDLE` command tests the functionality of the display's touch screen.
|
||||
It waits for a specific time period without any touch input.
|
||||
|
||||
The command requires one input parameter:
|
||||
* The timeout in seconds
|
||||
|
||||
If a touch activity is detected within the specified timeout, the command will return an `ERROR TOUCH DETECTED`.
|
||||
|
||||
Example - wait ten seconds for no touch input:
|
||||
```
|
||||
TOUCH_IDLE 10
|
||||
OK
|
||||
```
|
||||
|
||||
### TOUCH_POWER
|
||||
The `TOUCH_POWER` command tests the functionality of touch layer power supply
|
||||
|
||||
The command requires one input parameter:
|
||||
* The timeout in milliseconds
|
||||
|
||||
The powers up the touch layer and waits for a specific time period so that measurement can be done by test equipment.
|
||||
|
||||
Example - wait ten milliseconds for touch power measurement:
|
||||
```
|
||||
TOUCH_POWER 10
|
||||
OK
|
||||
```
|
||||
|
||||
### SENS
|
||||
The `SENS` command is used to evaluating the touch screen sensitivity.
|
||||
It draws a filled box around the touch coordinates.
|
||||
It takes one input parameter, a sensitivity, a decimal value representing sensitivity.
|
||||
Please note that the sensitivity value is model-dependent.
|
||||
|
||||
It's important to mention that this command does not return any output.
|
||||
A device restart is required to stop this operation.
|
||||
### reboot
|
||||
This command initiates device reboot.
|
||||
|
||||
Example:
|
||||
```
|
||||
SENS 12
|
||||
```
|
||||
|
||||
### TOUCH VERSION
|
||||
Allows you to read the version of the touch screen controller, if its supported by the device.
|
||||
The command returns `OK` followed by the version number.
|
||||
|
||||
Example:
|
||||
```
|
||||
TOUCH VERSION
|
||||
OK 167
|
||||
```
|
||||
|
||||
### PWM
|
||||
The `PWM` command sets the display backlight using PWM (Pulse Width Modulation).
|
||||
This command takes one input parameter, a decimal value between 0 to 255, and adjusts the PWM output to control the display LED backlight.
|
||||
|
||||
Example::
|
||||
```
|
||||
DISP 128
|
||||
reboot
|
||||
OK
|
||||
```
|
||||
|
||||
### SD
|
||||
The `SD` command initiates a simple test of the SD card.
|
||||
The test includes writing and reading back a few blocks of data and comparing them for equality.
|
||||
|
||||
Possible error return codes are:
|
||||
|
||||
- `ERROR NOCARD` - Indicates that no SD card is present
|
||||
- `ERROR sdcard_write_blocks (n)` - Indicates a write failure to the N-th block
|
||||
- `ERROR sdcard_read_blocks (n)` - Indicates a read failure from the N-th block
|
||||
- `ERROR DATA MISMATCH` - Indicates a mismatch between the read data and the written data
|
||||
- `OK` - Indicates that the test has passed successfully
|
||||
|
||||
Note: the command returns `UNKNOWN` for models without the SD card support
|
||||
### boardloader-version
|
||||
Retrieves the version of the boardloader. The command returns `OK` followed by the version in the format `<major>.<minor>.<patch>`.
|
||||
|
||||
Example:
|
||||
```
|
||||
SD
|
||||
OK
|
||||
```
|
||||
|
||||
### SBU
|
||||
The `SBU` command allows you to set the states of SBU1 and SBU2 pins.
|
||||
It takes one input parameter, representing the state of both pins (00, 01, 10 or 11), and sets the corresponding output pins accordingly.
|
||||
|
||||
Example:
|
||||
```
|
||||
// sets SBU1 <- 1, SBU2 <- 0
|
||||
|
||||
SBU 10
|
||||
OK
|
||||
```
|
||||
|
||||
|
||||
### HAPTIC
|
||||
The `HAPTIC` command allows you to test the functionality of the device's haptic driver.
|
||||
It takes one input parameter, representing the duration of the vibration in milliseconds.
|
||||
The device only vibrates if there is motor connected to the haptic driver, otherwise the effect needs to be
|
||||
measured by an oscilloscope.
|
||||
|
||||
Example:
|
||||
```
|
||||
// runs the driver for 3000 ms
|
||||
|
||||
HAPTIC 3000
|
||||
OK
|
||||
```
|
||||
|
||||
### RGB_LED
|
||||
The `RGB_LED` command allows you to test the functionality of the device's RGB LED.
|
||||
It takes three input parameters, representing the intensity of the red, green, and blue color components.
|
||||
Each component is a decimal value between 0 and 255.
|
||||
|
||||
Example:
|
||||
```
|
||||
// sets the RGB LED to red
|
||||
|
||||
RGB_LED 255 0 0
|
||||
OK
|
||||
```
|
||||
|
||||
### OTP READ
|
||||
The `OTP READ` command is utilized to retrieve a string parameter from the device's OTP memory.
|
||||
This string typically contains information identifying the model and production batch of the device.
|
||||
The command always returns OK followed by the read value.
|
||||
If the OTP memory has not been written yet, it returns a special response: OK (null).
|
||||
|
||||
Example:
|
||||
```
|
||||
OTP READ
|
||||
OK (null)
|
||||
```
|
||||
|
||||
### OTP WRITE
|
||||
The `OTP WRITE` command enables you to store a string parameter (which can be used to identify the model and production batch, for instance) into the device's OTP memory.
|
||||
The parameter can be up to 31 characters in length.
|
||||
|
||||
The standard format is `<internal_model>-<YYMMDD>`, where YYMMDD represents the provisioning date. In case of Model T the `internal_model` is `TREZOR2`.
|
||||
|
||||
Example:
|
||||
```
|
||||
OTP WRITE T2B1-231231
|
||||
OK
|
||||
```
|
||||
|
||||
### VARIANT
|
||||
The `VARIANT` command writes up to 31 decimal values (representing device variant options), each ranging from 0 to 255, and delimited by spaces, into the OTP memory. This command should be called after the `LOCK` command was successfully executed.
|
||||
|
||||
The standard format is `VARIANT <unit_color> <unit_btconly> <unit_packaging>`.
|
||||
|
||||
Example (to write 3 bytes into OTP memory):
|
||||
```
|
||||
VARIANT 3 0 2
|
||||
```
|
||||
|
||||
### VARIANT READ
|
||||
The `VARIANT READ` command allows you to read 32 bytes of stored variant data (representing device variant options), each ranging from 0 to 255, and delimited by spaces. The first byte is the format version, followed by the bytes written using the VARIANT command and padded with null bytes.
|
||||
|
||||
Example:
|
||||
```
|
||||
VARIANT READ
|
||||
OK 1 3 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
```
|
||||
|
||||
### FIRMWARE VERSION
|
||||
Returns the version of the prodtest firmware.
|
||||
The command returns `OK` followed by the version in the format `<major>.<minor>.<patch>`.
|
||||
|
||||
Example:
|
||||
```
|
||||
FIRMWARE VERSION
|
||||
boardloader-version
|
||||
OK 0.2.6
|
||||
```
|
||||
|
||||
### BOOTLOADER VERSION
|
||||
Returns the version of the bootlaoder.
|
||||
The command returns `OK` followed by the version in the format `<major>.<minor>.<patch>`.
|
||||
### bootloader-version
|
||||
Retrives the version of the bootlaoder. The command returns `OK` followed by the version in the format `<major>.<minor>.<patch>`.
|
||||
|
||||
Example:
|
||||
```
|
||||
BOOTLOADER VERSION
|
||||
bootloader-version
|
||||
OK 2.1.7
|
||||
```
|
||||
|
||||
### BOARDLOADER VERSION
|
||||
Returns the version of the boardloader.
|
||||
### button-test
|
||||
The `button-test` command tests the functionality of the device's hardware buttons. It waits for the user to press and release a specified button in a designated timeout period.
|
||||
|
||||
`button-test <button> <timeout>`
|
||||
|
||||
* `button` specifies the expected button or combination of buttons, with possible values: `left`, `right`, `left+right`, `power`.
|
||||
* `timeout` specifies the timeout duration in milliseconds
|
||||
|
||||
If the specified button or button combination is not detected within the timeout, the command will respond with `timeout` error.
|
||||
|
||||
Example (to wait for 5 seconds for the left button):
|
||||
```
|
||||
button-test left 5000
|
||||
# Waiting for the button press...
|
||||
# Waiting for the button release...
|
||||
OK
|
||||
```
|
||||
|
||||
### display-border
|
||||
The `display-border` command draws a single white border around the screen on a black background.
|
||||
|
||||
Example:
|
||||
```
|
||||
display-border
|
||||
# Drawing display border...
|
||||
OK
|
||||
```
|
||||
|
||||
### display-bars
|
||||
Draws vertical color bars on the screen according to a specified string of color codes.
|
||||
|
||||
`display-bars <color-string>`
|
||||
|
||||
* `color-string` - A string where each character represents one vertical bar and its corresponding color.
|
||||
|
||||
Each character in the parameter represents a vertical bar and its corresponding color (R for red, B for blue, W for white, and any other character for black). The total number of characters determines the number of bars.
|
||||
|
||||
NOTE: On monochromatic displays, the characters R, G, and B are all interpreted as white.
|
||||
|
||||
Example (to draw 6 vertical bars - red, black, green, black, blue and black):
|
||||
```
|
||||
display-bars RxGxBx
|
||||
# Drawing 6 vertical bars...
|
||||
OK
|
||||
```
|
||||
|
||||
### display-set-backlight
|
||||
Adjusts the display backlight level.
|
||||
|
||||
`display-set-backlight <level>`
|
||||
|
||||
* `level` - A value between 0 (0%) and 255 (100%).
|
||||
|
||||
Example:
|
||||
```
|
||||
display-set-backlight 128
|
||||
# Updating display backlight level to 128...
|
||||
OK
|
||||
```
|
||||
|
||||
### get-cpuid
|
||||
Reads a 96-bit long unique ID stored in the device's CPU. The command returns `OK` followed by a 24-digit hexadecimal value representing the unique ID.
|
||||
|
||||
Example:
|
||||
```
|
||||
get-cpuid
|
||||
OK 2F0079001951354861125762
|
||||
```
|
||||
|
||||
### haptic-test
|
||||
Test the functionality of the device's haptic actuator. It takes one input parameter, representing the duration of the vibration in milliseconds.
|
||||
|
||||
The device only vibrates if there is motor connected to the haptic driver, otherwise the effect needs to be measured by an oscilloscope.
|
||||
|
||||
Example (runs the driver for 3s):
|
||||
```
|
||||
haptic-test 3000
|
||||
# Running haptic feedback test for 3000 ms...
|
||||
OK
|
||||
```
|
||||
|
||||
### touch-test
|
||||
Tests the functionality of the display's touch screen. It draws a filled rectangle in one of the four display quadrants and waits for user interaction.
|
||||
|
||||
`touch-test <quadrant> <timeout>`
|
||||
|
||||
* `quadrant` - A value between 0 and 3, determining the quadrant where the rectangle will be drawn.
|
||||
* `timeout` - The timeout duration in milliseconds.
|
||||
|
||||
If the display is not touched within the specified timeout, the command will return a `timeout` error.
|
||||
|
||||
The command does not check whether the touch point lies within the quadrant or not. It only returns the x and y coordinate of the touch point.
|
||||
|
||||
Example (drawing a rectangle in the top-left quadrant and waiting for 5 seconds for touch input):
|
||||
```
|
||||
touch-test 0 5000
|
||||
# Initializing the touch controller...
|
||||
# Waiting for a touch for 5000 ms...
|
||||
OK 68 246
|
||||
```
|
||||
|
||||
### touch-test-custom
|
||||
Tests the functionality of the display's touch screen by drawing a filled rectangle at custom coordinates and waiting for user interaction.
|
||||
|
||||
`touch-test-custom <x> <y> <width> <height> <timeout>`
|
||||
|
||||
* `x` - The x-coordinate of the top-left corner of the rectangle.
|
||||
* `y` - The y-coordinate of the top-left corner of the rectangle.
|
||||
* `width` - The width of the rectangle.
|
||||
* `height` - The height of the rectangle.
|
||||
* `timeout` - The timeout duration in milliseconds.
|
||||
|
||||
If the display is not touched within the specified timeout, the command returns a `timeout` error.
|
||||
|
||||
The device reports touch events, including coordinates and timestamps (in milliseconds). The correctness of the touch point is not validated and is left to the test equipment.
|
||||
|
||||
The test ends after the first lift-up event.
|
||||
|
||||
Example (drawing a 100x100 rectangle at position (10,10) and waiting for 15 seconds for touch input):
|
||||
```
|
||||
touch-test-custom 10 10 100 100 15000
|
||||
# Initializing the touch controller...
|
||||
# Drawing a rectangle at [10, 10] with size [100 x 100]...
|
||||
# Waiting for a touch for 15000 ms...
|
||||
PROGRESS start 86 76 29585228
|
||||
PROGRESS move 86 77 29585738
|
||||
PROGRESS move 86 88 29585811
|
||||
PROGRESS end 86 90 29586085
|
||||
OK
|
||||
```
|
||||
|
||||
### touch-test-idle
|
||||
Tests the functionality of the display's touch screen by verifying that no touch activity occurs within a specified time period.
|
||||
|
||||
`touch-test-idle <timeout>`
|
||||
|
||||
* `timeout` - The duration to wait, in milliseconds, during which no touch input should be detected.
|
||||
|
||||
If any touch activity is detected within the specified timeout, the command returns an error.
|
||||
|
||||
Example (wait 10 seconds to ensure no touch input is detected):
|
||||
```
|
||||
> touch-test-idle 10000
|
||||
# Initializing the touch controller...
|
||||
# Don't touch the screen for 10000 ms...
|
||||
OK
|
||||
```
|
||||
|
||||
### touch-test-power
|
||||
Tests the functionality of the touch layer's power supply. This command powers up the touch layer and waits for a specified time period, allowing test equipment to perform measurements.
|
||||
|
||||
`touch-test-power <timeout>`
|
||||
|
||||
* `timeout` - The duration to keep the touch layer powered, in milliseconds.
|
||||
|
||||
Example (power the touch layer for 10 seconds for measurement)t):
|
||||
```
|
||||
> touch-test-power 10000
|
||||
# Setting touch controller power for 10000 ms...
|
||||
OK
|
||||
```
|
||||
|
||||
### touch-test-sensitivity
|
||||
This command evaluates the touch screen's sensitivity by drawing a filled box around the touch coordinates.
|
||||
|
||||
`touch-test-sensitivity <sensitivity>`
|
||||
|
||||
* `sensitivity` - A decimal value representing the sensitivity level, ranging from 0 to 255.
|
||||
|
||||
NOTE: The sensitivity value is model-dependent and may vary based on the device.
|
||||
|
||||
This command does not produce any output. To stop the test, you must press CTRL+C or reboot the device.
|
||||
|
||||
Example:
|
||||
```
|
||||
touch-test-sensitivity 12
|
||||
# Initializing the touch controller...
|
||||
# Setting touch controller sensitivity to 12...
|
||||
# Running touch controller test...
|
||||
# Press CTRL+C for exit.
|
||||
ERROR abort
|
||||
```
|
||||
|
||||
### touch-version
|
||||
Retrieves the version of the touch screen controller if supported by the device. The command returns `OK`, followed by the version number.
|
||||
|
||||
Example:
|
||||
```
|
||||
touch-version
|
||||
# Initializing the touch controller...
|
||||
# Reading the touch controller version...
|
||||
OK 1
|
||||
```
|
||||
|
||||
### sdcard-test
|
||||
Initiates a basic test of the SD card by writing a few blocks of data, reading them back, and verifying their integrity through comparison.
|
||||
|
||||
Possible error return codes are:
|
||||
|
||||
- `no-card` - Indicates that no SD card is present
|
||||
- `error` - An I/O error occured (with additional details provided in the description field)
|
||||
|
||||
Example:
|
||||
```
|
||||
sdcard-test
|
||||
OK
|
||||
```
|
||||
|
||||
### sbu-set
|
||||
Sets the logical states of SBU1 and SBU2 pins. It takes tow input parameters, representing the state (0 or 1) of each pin.
|
||||
|
||||
`sbu-set <sbu1> <sbu2>`
|
||||
|
||||
Example:
|
||||
```
|
||||
SBU 1 0
|
||||
# Setting SBU1 to 1 and SBU2 to 0...
|
||||
OK
|
||||
```
|
||||
|
||||
### rgbled-set
|
||||
The `rgbled-set` command allows you to test the functionality of the device's RGB LED. It takes three input parameters, representing the intensity of the red, green, and blue color components.
|
||||
Each component is a decimal value between 0 and 255.
|
||||
|
||||
`rgbled-set <r> <g> <b>`
|
||||
|
||||
Example:
|
||||
```
|
||||
rgbled-set 255 0 0
|
||||
# Setting the RGB LED color to [255, 0, 0]...
|
||||
OK
|
||||
```
|
||||
|
||||
### otp-batch-read
|
||||
Retrieves the batch string from the device's OTP memory. The batch string identifies the model and production batch of the device.
|
||||
|
||||
If the OTP memory has not been written yet, it returns error code `no-data`.
|
||||
|
||||
Example:
|
||||
```
|
||||
# Reading device OTP memory...
|
||||
# Bytes read: <hexadecimal string>
|
||||
ERROR no-data "OTP block is empty."
|
||||
```
|
||||
|
||||
### otp-batch-write
|
||||
Writes the batch string to the device's OTP memory. The batch string identifies the model and production batch of the device.
|
||||
|
||||
The batch string can be up to 31 characters in length. The standard format is `<internal_model>-<YYMMDD>`, where YYMMDD represents the provisioning date. For Model T, the `internal_model` is `TREZOR2`.
|
||||
|
||||
In non-production firmware, you must include `--execute` as the last parameter to write the data to the OTP memory. Conversely, in production firmware, you can use `--dry-run` as the last parameter to simulate the command without actually writing to the OTP memory.
|
||||
|
||||
Example:
|
||||
```
|
||||
otp-batch-write T2B1-231231 --dry-run
|
||||
#
|
||||
# !!! It's a dry run, OTP will be left unchanged.
|
||||
# !!! Use '--execute' switch to write to OTP memory.
|
||||
#
|
||||
# Writing device batch info into OTP memory...
|
||||
# Bytes written: 543242312D323331323331000000000000000000000000000000000000000000
|
||||
# Locking OTP block...
|
||||
```
|
||||
|
||||
### otp-variant-write
|
||||
Writes up to 31 decimal values, each representing device variant options, to device's OTP memory. Each value must range from 0 to 255.
|
||||
|
||||
This command should be called after the `optiga-lock` command was successfully completed.
|
||||
|
||||
Currently, three values are required during production:
|
||||
`otp-variant-write <unit_color> <unit_btconly> <unit_packaging>`.
|
||||
|
||||
In non-production firmware, you must include `--execute` as the last parameter to write the data to the OTP memory. Conversely, in production firmware, you can use `--dry-run` as the last parameter to simulate the command without actually writing to the OTP memory.
|
||||
|
||||
Example (to write 3 bytes into OTP memory):
|
||||
```
|
||||
otp-variant-write 2 3 5 --dry-run
|
||||
#
|
||||
# !!! It's a dry run, OTP will be left unchanged.
|
||||
# !!! Use '--execute' switch to write to OTP memory.
|
||||
#
|
||||
# Writing device batch info into OTP memory...
|
||||
# Bytes written: 0102030500000000000000000000000000000000000000000000000000000000
|
||||
# Locking OTP block...
|
||||
OK
|
||||
```
|
||||
|
||||
### otp-variant-read
|
||||
Retrieves 32 bytes stored in the device's OTP memory block, representing device variant options. Each value ranges from 0 to 255. The first byte indicates the format version, followed by the bytes written using the `otp-variant-write command`, and padded with zero bytes.
|
||||
|
||||
Example:
|
||||
```
|
||||
otp-variant-read
|
||||
# Reading device OTP memory...
|
||||
# Bytes read: <hexadecimal string>
|
||||
OK 1 2 3 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
```
|
||||
|
||||
### prodtest-version
|
||||
Retrieves the version of the prodtest firmware.
|
||||
The command returns `OK` followed by the version in the format `<major>.<minor>.<patch>`.
|
||||
|
||||
Example:
|
||||
```
|
||||
BOARDLOADER VERSION
|
||||
prodtest-version
|
||||
OK 0.2.6
|
||||
```
|
||||
|
||||
### WIPE
|
||||
This command invalidates the current firmware in the flash memory by erasing its beginning, including metadata.
|
||||
After performing this operation, it displays the text "WIPED" on the screen and returns the response OK.
|
||||
### prodtest-wipe
|
||||
This command invalidates the current firmware in the flash memory by erasing its header, preventing it from starting after the next reboot. After completing this operation, it displays the text "WIPED" on the screen.
|
||||
|
||||
Example:
|
||||
```
|
||||
WIPE
|
||||
prodtest-wipe
|
||||
OK
|
||||
```
|
||||
|
||||
### REBOOT
|
||||
This command initiates device reboot. No response, as the device reboots immediately after receiving the command.
|
||||
Example:
|
||||
```
|
||||
REBOOT
|
||||
```
|
||||
|
||||
### OPTIGAID READ
|
||||
Returns the coprocessor UID of the Optiga chip as a 27 byte hexadecimal string.
|
||||
### optiga-id-read
|
||||
Retrieves the coprocessor UID of the Optiga chip as a 27 byte hexadecimal string.
|
||||
|
||||
Example:
|
||||
```
|
||||
OPTIGAID READ
|
||||
optiga-id-read
|
||||
OK CD16339401001C000100000A023EA600190057006E801010712440
|
||||
```
|
||||
|
||||
### CERTINF READ
|
||||
Returns the X.509 certificate issued by Infineon for the Optiga chip.
|
||||
### optiga-certinf-read
|
||||
Retrieves the X.509 certificate issued by Infineon for the Optiga chip.
|
||||
|
||||
Example:
|
||||
```
|
||||
CERTINF READ
|
||||
optiga-certinf-read
|
||||
OK <hexadecimal string>
|
||||
```
|
||||
|
||||
### CERTDEV WRITE
|
||||
### optiga-certinf-write
|
||||
Writes the X.509 certificate issued by the Trezor Company for the device.
|
||||
|
||||
Example:
|
||||
```
|
||||
CERTDEV WRITE <hexadecimal string>
|
||||
optiga-certinf-write <hexadecimal string>
|
||||
OK
|
||||
```
|
||||
|
||||
### CERTDEV READ
|
||||
Returns the X.509 certificate issued by the Trezor Company for the device.
|
||||
### optiga-certdev-red
|
||||
Retrieves the X.509 certificate issued by the Trezor Company for the device.
|
||||
|
||||
Example:
|
||||
```
|
||||
CERTDEV READ
|
||||
optiga-certdev-read
|
||||
OK <hexadecimal string>
|
||||
```
|
||||
|
||||
### CERTFIDO WRITE
|
||||
### optiga-certfido-write
|
||||
Writes the X.509 certificate issued by the Trezor Company for the FIDO attestation key.
|
||||
|
||||
Example:
|
||||
```
|
||||
CERTFIDO WRITE <hexadecimal string>
|
||||
optiga-certfido-write <hexadecimal string>
|
||||
OK
|
||||
```
|
||||
|
||||
### CERTFIDO READ
|
||||
Returns the X.509 certificate issued by the Trezor Company for the FIDO attestation key.
|
||||
### optiga-certfido-read
|
||||
Retrieves the X.509 certificate issued by the Trezor Company for the FIDO attestation key.
|
||||
|
||||
Example:
|
||||
```
|
||||
CERTFIDO READ
|
||||
optiga-certfido-read
|
||||
OK <hexadecimal string>
|
||||
```
|
||||
|
||||
### KEYFIDO WRITE
|
||||
### optiga-keyfido-write
|
||||
Decrypts and stores an encrypted FIDO attestation private key into Optiga. No return value.
|
||||
|
||||
Example:
|
||||
```
|
||||
KEYFIDO WRITE <hexadecimal string>
|
||||
optiga-keyfido-write <hexadecimal string>
|
||||
OK
|
||||
```
|
||||
|
||||
### KEYFIDO READ
|
||||
Returns the x-coordinate of the FIDO attestation public key stored in Optiga. Can be executed only before the LOCK command is called.
|
||||
### optiga-keyfido-read
|
||||
Retrieves the x-coordinate of the FIDO attestation public key stored in Optiga. Can be executed only before the LOCK command is called.
|
||||
|
||||
This command can be used to verify that the FIDO attestation key was decrypted and stored correctly by verifying that the returned string of bytes appears in the FIDO attestation certificate.
|
||||
|
||||
Example:
|
||||
```
|
||||
KEYFIDO READ
|
||||
optiga-keyfido-read
|
||||
OK 0D35A613358EDAB4CA04D05DD716546CD97973DE58516AF6A8F69BEE89BEFAA1
|
||||
```
|
||||
|
||||
### LOCK
|
||||
Configures the metadata for Optiga's data objects that should be set up during provisioning and locks them. No return value.
|
||||
### optiga-lock
|
||||
Configures the metadata for Optiga's data objects that should be set up during provisioning and locks them.
|
||||
|
||||
Example:
|
||||
```
|
||||
LOCK
|
||||
optiga-lock
|
||||
OK
|
||||
```
|
||||
|
||||
### CHECK LOCKED
|
||||
### optiga-lock-check
|
||||
Returns `YES` if all of Optiga's data objects that should be set up during provisioning are locked. If not, then `NO` is returned.
|
||||
|
||||
Example:
|
||||
```
|
||||
CHECK LOCKED
|
||||
optiga-lock-check
|
||||
OK YES
|
||||
```
|
||||
|
||||
### SEC READ
|
||||
Returns the value of Optiga's security event counter as a 1 byte hexadecimal value.
|
||||
### optiga-counter-read
|
||||
Retrieves the value of Optiga's security event counter as a 1 byte hexadecimal value.
|
||||
|
||||
Example:
|
||||
```
|
||||
SEC READ
|
||||
optiga-counter-read
|
||||
OK 0E
|
||||
```
|
||||
|
45
core/embed/projects/prodtest/cmd/prodtest_boardloader.c
Normal file
45
core/embed/projects/prodtest/cmd/prodtest_boardloader.c
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <rtl/cli.h>
|
||||
#include <util/board_capabilities.h>
|
||||
|
||||
static void prodtest_boardloader_version(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Parsing boardloader capabilities...");
|
||||
parse_boardloader_capabilities();
|
||||
|
||||
const boardloader_version_t* v = get_boardloader_version();
|
||||
cli_ok(cli, "%d.%d.%d", v->version_major, v->version_minor, v->version_patch);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "boardloader-version",
|
||||
.func = prodtest_boardloader_version,
|
||||
.info = "Retrieve the boardloader version",
|
||||
.args = ""
|
||||
);
|
61
core/embed/projects/prodtest/cmd/prodtest_bootloader.c
Normal file
61
core/embed/projects/prodtest/cmd/prodtest_bootloader.c
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <rtl/cli.h>
|
||||
#include <sys/mpu.h>
|
||||
#include <util/image.h>
|
||||
|
||||
static void prodtest_bootloader_version(cli_t *cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t v = 0;
|
||||
|
||||
mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_BOOTUPDATE);
|
||||
|
||||
cli_trace(cli, "Reading bootloader image header..");
|
||||
|
||||
const image_header *header =
|
||||
read_image_header((const uint8_t *)BOOTLOADER_START,
|
||||
BOOTLOADER_IMAGE_MAGIC, BOOTLOADER_MAXSIZE);
|
||||
|
||||
if (header != NULL) {
|
||||
v = header->version;
|
||||
} else {
|
||||
cli_error(cli, CLI_ERROR, "No valid bootloader header found.");
|
||||
return;
|
||||
}
|
||||
|
||||
mpu_restore(mpu_mode);
|
||||
|
||||
cli_ok(cli, "%d.%d.%d", v & 0xFF, (v >> 8) & 0xFF, (v >> 16) & 0xFF);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "bootloader-version",
|
||||
.func = prodtest_bootloader_version,
|
||||
.info = "Retrieve the bootloader version",
|
||||
.args = ""
|
||||
);
|
147
core/embed/projects/prodtest/cmd/prodtest_button.c
Normal file
147
core/embed/projects/prodtest/cmd/prodtest_button.c
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <io/button.h>
|
||||
#include <rtl/cli.h>
|
||||
#include <sys/systick.h>
|
||||
|
||||
static void test_single_button(cli_t* cli, uint32_t timeout, button_t btn) {
|
||||
uint32_t expire_time = ticks_timeout(timeout);
|
||||
|
||||
cli_trace(cli, "Waiting for the button press...");
|
||||
|
||||
while (button_get_event() != (btn | BTN_EVT_DOWN)) {
|
||||
if (ticks_expired(expire_time)) {
|
||||
cli_error(cli, CLI_ERROR_TIMEOUT, "");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_aborted(cli)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cli_trace(cli, "Waiting for the button release...");
|
||||
|
||||
while (button_get_event() != (btn | BTN_EVT_UP)) {
|
||||
if (ticks_expired(expire_time)) {
|
||||
cli_error(cli, CLI_ERROR_TIMEOUT, "");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_aborted(cli)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void test_button_combination(cli_t* cli, uint32_t timeout, button_t btn1,
|
||||
button_t btn2) {
|
||||
uint32_t expire_time = ticks_timeout(timeout);
|
||||
|
||||
cli_trace(cli, "Waiting for button combination to be pressed...");
|
||||
|
||||
while (true) {
|
||||
// Event must be read before calling `button_is_down()`
|
||||
button_get_event();
|
||||
|
||||
if (button_is_down(btn1) && button_is_down(btn2)) {
|
||||
break;
|
||||
} else if (ticks_expired(expire_time)) {
|
||||
cli_error(cli, CLI_ERROR_TIMEOUT, "");
|
||||
return;
|
||||
} else if (cli_aborted(cli)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cli_trace(cli, "Waiting for buttons to be released...");
|
||||
|
||||
while (true) {
|
||||
// Event must be read before calling `button_is_down()`
|
||||
button_get_event();
|
||||
|
||||
if (!button_is_down(btn1) && !button_is_down(btn2)) {
|
||||
break;
|
||||
} else if (ticks_expired(expire_time)) {
|
||||
cli_error(cli, CLI_ERROR_TIMEOUT, "");
|
||||
return;
|
||||
} else if (cli_aborted(cli)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_button_test(cli_t* cli) {
|
||||
const char* button = cli_arg(cli, "button");
|
||||
int btn1 = -1;
|
||||
int btn2 = -1;
|
||||
|
||||
if (strcmp(button, "left") == 0) {
|
||||
btn1 = BTN_LEFT;
|
||||
} else if (strcmp(button, "right") == 0) {
|
||||
btn1 = BTN_RIGHT;
|
||||
} else if (strcmp(button, "left+right") == 0) {
|
||||
btn1 = BTN_LEFT;
|
||||
btn2 = BTN_RIGHT;
|
||||
} else if (strcmp(button, "power") == 0) {
|
||||
btn1 = BTN_POWER;
|
||||
} else {
|
||||
cli_error_arg(cli,
|
||||
"Expecting button name - left, right, left+right or power.");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t timeout = 0;
|
||||
|
||||
if (!cli_arg_uint32(cli, "timeout", &timeout)) {
|
||||
cli_error_arg(cli, "Expecting timeout in milliseconds.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 2) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (btn2 >= 0) {
|
||||
test_button_combination(cli, timeout, btn1, btn2);
|
||||
} else {
|
||||
test_single_button(cli, timeout, btn1);
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "button-test",
|
||||
.func = prodtest_button_test,
|
||||
.info = "Test the hardware buttons",
|
||||
.args = "<button> <timeout>"
|
||||
);
|
||||
|
||||
#endif // USE_BUTTON
|
141
core/embed/projects/prodtest/cmd/prodtest_display.c
Normal file
141
core/embed/projects/prodtest/cmd/prodtest_display.c
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <gfx/gfx_draw.h>
|
||||
#include <io/display.h>
|
||||
#include <rtl/cli.h>
|
||||
|
||||
static void prodtest_display_border(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
gfx_clear();
|
||||
|
||||
cli_trace(cli, "Drawing display border...");
|
||||
|
||||
gfx_rect_t r_out = gfx_rect_wh(0, 0, DISPLAY_RESX, DISPLAY_RESY);
|
||||
gfx_rect_t r_in = gfx_rect_wh(1, 1, DISPLAY_RESX - 2, DISPLAY_RESY - 2);
|
||||
|
||||
gfx_draw_bar(r_out, COLOR_WHITE);
|
||||
gfx_draw_bar(r_in, COLOR_BLACK);
|
||||
|
||||
display_refresh();
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_display_bars(cli_t* cli) {
|
||||
gfx_clear();
|
||||
|
||||
const char* colors = cli_arg(cli, "colors");
|
||||
size_t color_count = strlen(colors);
|
||||
|
||||
if (cli_arg_count(cli) > 1) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
bool invalid_color = false;
|
||||
|
||||
cli_trace(cli, "Drawing %d vertical bars...", color_count);
|
||||
|
||||
for (size_t i = 0; i < color_count; i++) {
|
||||
gfx_color_t c = COLOR_BLACK; // black
|
||||
switch (colors[i]) {
|
||||
case 'R':
|
||||
case 'r':
|
||||
c = gfx_color_rgb(255, 0, 0);
|
||||
break;
|
||||
case 'G':
|
||||
case 'g':
|
||||
c = gfx_color_rgb(0, 255, 0);
|
||||
break;
|
||||
case 'B':
|
||||
case 'b':
|
||||
c = gfx_color_rgb(0, 0, 255);
|
||||
break;
|
||||
case 'W':
|
||||
case 'w':
|
||||
c = COLOR_WHITE;
|
||||
break;
|
||||
default:
|
||||
invalid_color = true;
|
||||
break;
|
||||
}
|
||||
|
||||
int x1 = (DISPLAY_RESX * i) / color_count;
|
||||
int x2 = (DISPLAY_RESX * (i + 1)) / color_count;
|
||||
|
||||
gfx_draw_bar(gfx_rect(x1, 0, x2, DISPLAY_RESY), c);
|
||||
}
|
||||
|
||||
if (strlen(colors) == 0 || invalid_color) {
|
||||
cli_trace(cli, "Not valid color pattern (RGBW characters expected).");
|
||||
}
|
||||
|
||||
display_refresh();
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_display_set_backlight(cli_t* cli) {
|
||||
uint32_t level = 0;
|
||||
|
||||
if (!cli_arg_uint32(cli, "level", &level) || level > 255) {
|
||||
cli_error_arg(cli, "Expecting backlig level in range 0-255 (100%%).");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 1) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Updating display backlight level to %d...", level);
|
||||
display_set_backlight(level);
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "display-border",
|
||||
.func = prodtest_display_border,
|
||||
.info = "Display a border around the screen",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "display-bars",
|
||||
.func = prodtest_display_bars,
|
||||
.info = "Display vertical bars in different colors",
|
||||
.args = "<colors>"
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "display-set-backlight",
|
||||
.func = prodtest_display_set_backlight,
|
||||
.info = "Set the display backlight level",
|
||||
.args = "<level>"
|
||||
);
|
58
core/embed/projects/prodtest/cmd/prodtest_get_cpuid.c
Normal file
58
core/embed/projects/prodtest/cmd/prodtest_get_cpuid.c
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <trezor_bsp.h> // required by #ifdef STM32U5 below (see #4306 issue)
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <rtl/cli.h>
|
||||
#include <sys/mpu.h>
|
||||
|
||||
#ifdef STM32U5
|
||||
#include "stm32u5xx_ll_utils.h"
|
||||
#else
|
||||
#include "stm32f4xx_ll_utils.h"
|
||||
#endif
|
||||
|
||||
static void prodtest_get_cpuid(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t cpuid[3];
|
||||
|
||||
mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_OTP);
|
||||
cpuid[0] = LL_GetUID_Word0();
|
||||
cpuid[1] = LL_GetUID_Word1();
|
||||
cpuid[2] = LL_GetUID_Word2();
|
||||
mpu_restore(mpu_mode);
|
||||
|
||||
cli_ok_hexdata(cli, &cpuid, sizeof(cpuid));
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "get-cpuid",
|
||||
.func = prodtest_get_cpuid,
|
||||
.info = "Retrieve unique CPU ID",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
|
66
core/embed/projects/prodtest/cmd/prodtest_haptic.c
Normal file
66
core/embed/projects/prodtest/cmd/prodtest_haptic.c
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_HAPTIC
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <io/haptic.h>
|
||||
#include <rtl/cli.h>
|
||||
|
||||
static void prodtest_haptic_test(cli_t* cli) {
|
||||
uint32_t duration = 0; // ms
|
||||
|
||||
if (!cli_arg_uint32(cli, "duration", &duration) || duration > 5000) {
|
||||
cli_error_arg(cli, "Expecting time in milliseconds in range 0-5000.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 1) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!haptic_init()) {
|
||||
cli_error(cli, CLI_ERROR, "Haptic driver initialization failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Running haptic feedback test for %d ms...", duration);
|
||||
if (!haptic_test(duration)) {
|
||||
cli_error(cli, CLI_ERROR, "Haptic feedback test failed.");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
|
||||
cleanup:
|
||||
haptic_deinit();
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "haptic-test",
|
||||
.func = prodtest_haptic_test,
|
||||
.info = "Test the haptic feedback actuator",
|
||||
.args = "<duration>"
|
||||
);
|
||||
|
||||
#endif // USE_HAPTIC
|
61
core/embed/projects/prodtest/cmd/prodtest_help.c
Normal file
61
core/embed/projects/prodtest/cmd/prodtest_help.c
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <rtl/cli.h>
|
||||
|
||||
static void prodtest_help(cli_t* cli) {
|
||||
const char* prefix = cli_arg(cli, "prefix");
|
||||
size_t prefix_len = strlen(prefix);
|
||||
|
||||
if (cli_arg_count(cli) > 1) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefix_len > 0) {
|
||||
cli_trace(cli, "Available commands:");
|
||||
} else {
|
||||
cli_trace(cli, "Available commands (filtered):");
|
||||
}
|
||||
|
||||
extern cli_command_t _prodtest_cli_cmd_section_start;
|
||||
extern cli_command_t _prodtest_cli_cmd_section_end;
|
||||
|
||||
cli_command_t* cmd = &_prodtest_cli_cmd_section_start;
|
||||
|
||||
while (cmd < &_prodtest_cli_cmd_section_end) {
|
||||
if (cmd->name[0] != '$' && strncmp(cmd->name, prefix, prefix_len) == 0) {
|
||||
cli_trace(cli, " %s - %s", cmd->name, cmd->info);
|
||||
}
|
||||
cmd++;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "help",
|
||||
.func = prodtest_help,
|
||||
.info = "Dsiplay the list of available commands",
|
||||
.args = "[<prefix>]"
|
||||
);
|
@ -17,26 +17,38 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_OPTIGA
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <rtl/cli.h>
|
||||
#include <sec/optiga_commands.h>
|
||||
#include <sec/optiga_transport.h>
|
||||
#include <sec/secret.h>
|
||||
|
||||
#include "aes/aes.h"
|
||||
#include "buffer.h"
|
||||
#include "der.h"
|
||||
#include "ecdsa.h"
|
||||
#include "memzero.h"
|
||||
#include "nist256p1.h"
|
||||
#include "optiga_prodtest.h"
|
||||
#include "prodtest_common.h"
|
||||
#include "rand.h"
|
||||
#include "sha2.h"
|
||||
|
||||
#include "prodtest_optiga.h"
|
||||
|
||||
#ifdef USE_STORAGE_HWKEY
|
||||
#include <sec/secure_aes.h>
|
||||
#endif
|
||||
|
||||
#define OID_CERT_INF (OPTIGA_OID_CERT + 0)
|
||||
#define OID_CERT_DEV (OPTIGA_OID_CERT + 1)
|
||||
#define OID_CERT_FIDO (OPTIGA_OID_CERT + 2)
|
||||
#define OID_KEY_DEV (OPTIGA_OID_ECC_KEY + 0)
|
||||
#define OID_KEY_FIDO (OPTIGA_OID_ECC_KEY + 2)
|
||||
#define OID_KEY_PAIRING OPTIGA_OID_PTFBIND_SECRET
|
||||
#define OID_TRUST_ANCHOR (OPTIGA_OID_CA_CERT + 0)
|
||||
|
||||
typedef enum {
|
||||
OPTIGA_PAIRING_UNPAIRED = 0,
|
||||
OPTIGA_PAIRING_PAIRED,
|
||||
@ -70,7 +82,11 @@ static const optiga_metadata_item TYPE_PTFBIND =
|
||||
// (id-ce-authorityKeyIdentifier).
|
||||
const uint8_t OID_AUTHORITY_KEY_IDENTIFIER[] = {0x06, 0x03, 0x55, 0x1d, 0x23};
|
||||
|
||||
static bool optiga_paired(void) {
|
||||
// forward declaration
|
||||
static bool check_device_cert_chain(cli_t* cli, const uint8_t* chain,
|
||||
size_t chain_size);
|
||||
|
||||
static bool optiga_paired(cli_t* cli) {
|
||||
const char* details = "";
|
||||
|
||||
switch (optiga_pairing_state) {
|
||||
@ -98,18 +114,21 @@ static bool optiga_paired(void) {
|
||||
break;
|
||||
}
|
||||
|
||||
vcp_println("ERROR Optiga not paired (%s).", details);
|
||||
cli_error(cli, CLI_ERROR, "Optiga not paired (%s).", details);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool set_metadata(uint16_t oid, const optiga_metadata *metadata) {
|
||||
static bool set_metadata(cli_t* cli, uint16_t oid,
|
||||
const optiga_metadata* metadata, bool report_error) {
|
||||
uint8_t serialized[OPTIGA_MAX_METADATA_SIZE] = {0};
|
||||
size_t size = 0;
|
||||
optiga_result ret = optiga_serialize_metadata(metadata, serialized,
|
||||
sizeof(serialized), &size);
|
||||
if (OPTIGA_SUCCESS != ret) {
|
||||
vcp_println("ERROR optiga_serialize_metadata error %d for OID 0x%04x.", ret,
|
||||
oid);
|
||||
if (report_error) {
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"optiga_serialize_metadata error %d for OID 0x%04x.", ret, oid);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -118,26 +137,33 @@ static bool set_metadata(uint16_t oid, const optiga_metadata *metadata) {
|
||||
ret =
|
||||
optiga_get_data_object(oid, true, serialized, sizeof(serialized), &size);
|
||||
if (OPTIGA_SUCCESS != ret) {
|
||||
vcp_println("ERROR optiga_get_metadata error %d for OID 0x%04x.", ret, oid);
|
||||
if (report_error) {
|
||||
cli_error(cli, CLI_ERROR, "optiga_get_metadata error %d for OID 0x%04x.",
|
||||
ret, oid);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
optiga_metadata metadata_stored = {0};
|
||||
ret = optiga_parse_metadata(serialized, size, &metadata_stored);
|
||||
if (OPTIGA_SUCCESS != ret) {
|
||||
vcp_println("ERROR optiga_parse_metadata error %d.", ret);
|
||||
if (report_error) {
|
||||
cli_error(cli, CLI_ERROR, "optiga_parse_metadata error %d.", ret);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!optiga_compare_metadata(metadata, &metadata_stored)) {
|
||||
vcp_println("ERROR optiga_compare_metadata failed.");
|
||||
if (report_error) {
|
||||
cli_error(cli, CLI_ERROR, "optiga_compare_metadata failed.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void pair_optiga(void) {
|
||||
void pair_optiga(cli_t* cli) {
|
||||
uint8_t secret[SECRET_OPTIGA_KEY_LEN] = {0};
|
||||
|
||||
if (secret_optiga_get(secret) != sectrue) {
|
||||
@ -159,7 +185,8 @@ void pair_optiga(void) {
|
||||
metadata.change = OPTIGA_META_ACCESS_ALWAYS;
|
||||
metadata.execute = OPTIGA_META_ACCESS_ALWAYS;
|
||||
metadata.data_type = TYPE_PTFBIND;
|
||||
(void)set_metadata(OID_KEY_PAIRING, &metadata);
|
||||
(void)set_metadata(cli, OID_KEY_PAIRING, &metadata,
|
||||
false); // Ignore result.
|
||||
|
||||
// Store the pairing secret in OPTIGA.
|
||||
if (OPTIGA_SUCCESS != optiga_set_data_object(OID_KEY_PAIRING, false, secret,
|
||||
@ -209,14 +236,19 @@ void pair_optiga(void) {
|
||||
#define METADATA_SET_LOCKED(metadata)
|
||||
#endif
|
||||
|
||||
void optiga_lock(void) {
|
||||
if (!optiga_paired()) return;
|
||||
static void prodtest_optiga_lock(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!optiga_paired(cli)) return;
|
||||
|
||||
// Delete trust anchor.
|
||||
optiga_result ret =
|
||||
optiga_set_data_object(OID_TRUST_ANCHOR, false, (const uint8_t*)"\0", 1);
|
||||
if (OPTIGA_SUCCESS != ret) {
|
||||
vcp_println("ERROR optiga_set_data error %d for 0x%04x.", ret,
|
||||
cli_error(cli, CLI_ERROR, "optiga_set_data error %d for 0x%04x.", ret,
|
||||
OID_TRUST_ANCHOR);
|
||||
return;
|
||||
}
|
||||
@ -230,7 +262,7 @@ void optiga_lock(void) {
|
||||
metadata.change = OPTIGA_META_ACCESS_NEVER;
|
||||
metadata.read = OPTIGA_META_ACCESS_ALWAYS;
|
||||
metadata.execute = OPTIGA_META_ACCESS_ALWAYS;
|
||||
if (!set_metadata(OID_CERT_DEV, &metadata)) {
|
||||
if (!set_metadata(cli, OID_CERT_DEV, &metadata, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -240,7 +272,7 @@ void optiga_lock(void) {
|
||||
metadata.change = OPTIGA_META_ACCESS_NEVER;
|
||||
metadata.read = OPTIGA_META_ACCESS_ALWAYS;
|
||||
metadata.execute = OPTIGA_META_ACCESS_ALWAYS;
|
||||
if (!set_metadata(OID_CERT_FIDO, &metadata)) {
|
||||
if (!set_metadata(cli, OID_CERT_FIDO, &metadata, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -251,7 +283,7 @@ void optiga_lock(void) {
|
||||
metadata.read = OPTIGA_META_ACCESS_NEVER;
|
||||
metadata.execute = ACCESS_PAIRED;
|
||||
metadata.key_usage = KEY_USE_SIGN;
|
||||
if (!set_metadata(OID_KEY_DEV, &metadata)) {
|
||||
if (!set_metadata(cli, OID_KEY_DEV, &metadata, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -262,7 +294,7 @@ void optiga_lock(void) {
|
||||
metadata.read = OPTIGA_META_ACCESS_NEVER;
|
||||
metadata.execute = ACCESS_PAIRED;
|
||||
metadata.key_usage = KEY_USE_SIGN;
|
||||
if (!set_metadata(OID_KEY_FIDO, &metadata)) {
|
||||
if (!set_metadata(cli, OID_KEY_FIDO, &metadata, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -273,15 +305,15 @@ void optiga_lock(void) {
|
||||
metadata.read = OPTIGA_META_ACCESS_NEVER;
|
||||
metadata.execute = OPTIGA_META_ACCESS_ALWAYS;
|
||||
metadata.data_type = TYPE_PTFBIND;
|
||||
if (!set_metadata(OID_KEY_PAIRING, &metadata)) {
|
||||
if (!set_metadata(cli, OID_KEY_PAIRING, &metadata, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
vcp_println("OK");
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
optiga_locked_status get_optiga_locked_status(void) {
|
||||
if (!optiga_paired()) return OPTIGA_LOCKED_ERROR;
|
||||
optiga_locked_status get_optiga_locked_status(cli_t* cli) {
|
||||
if (!optiga_paired(cli)) return OPTIGA_LOCKED_ERROR;
|
||||
|
||||
const uint16_t oids[] = {OID_CERT_DEV, OID_CERT_FIDO, OID_KEY_DEV,
|
||||
OID_KEY_FIDO, OID_KEY_PAIRING};
|
||||
@ -295,8 +327,8 @@ optiga_locked_status get_optiga_locked_status(void) {
|
||||
optiga_get_data_object(oids[i], true, metadata_buffer,
|
||||
sizeof(metadata_buffer), &metadata_size);
|
||||
if (OPTIGA_SUCCESS != ret) {
|
||||
vcp_println("ERROR optiga_get_metadata error %d for OID 0x%04x.", ret,
|
||||
oids[i]);
|
||||
cli_error(cli, CLI_ERROR, "optiga_get_metadata error %d for OID 0x%04x.",
|
||||
ret, oids[i]);
|
||||
return OPTIGA_LOCKED_ERROR;
|
||||
}
|
||||
|
||||
@ -304,7 +336,7 @@ optiga_locked_status get_optiga_locked_status(void) {
|
||||
ret =
|
||||
optiga_parse_metadata(metadata_buffer, metadata_size, &stored_metadata);
|
||||
if (OPTIGA_SUCCESS != ret) {
|
||||
vcp_println("ERROR optiga_parse_metadata error %d.", ret);
|
||||
cli_error(cli, CLI_ERROR, "optiga_parse_metadata error %d.", ret);
|
||||
return OPTIGA_LOCKED_ERROR;
|
||||
}
|
||||
|
||||
@ -316,13 +348,18 @@ optiga_locked_status get_optiga_locked_status(void) {
|
||||
return OPTIGA_LOCKED_TRUE;
|
||||
}
|
||||
|
||||
void check_locked(void) {
|
||||
switch (get_optiga_locked_status()) {
|
||||
static void prodtest_optiga_lock_check(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (get_optiga_locked_status(cli)) {
|
||||
case OPTIGA_LOCKED_TRUE:
|
||||
vcp_println("OK YES");
|
||||
cli_ok(cli, "YES");
|
||||
break;
|
||||
case OPTIGA_LOCKED_FALSE:
|
||||
vcp_println("OK NO");
|
||||
cli_ok(cli, "NO");
|
||||
break;
|
||||
default:
|
||||
// Error reported by get_optiga_locked_status().
|
||||
@ -330,8 +367,13 @@ void check_locked(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void optigaid_read(void) {
|
||||
if (!optiga_paired()) return;
|
||||
static void prodtest_optiga_id_read(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!optiga_paired(cli)) return;
|
||||
|
||||
uint8_t optiga_id[27] = {0};
|
||||
size_t optiga_id_size = 0;
|
||||
@ -340,24 +382,29 @@ void optigaid_read(void) {
|
||||
optiga_get_data_object(OPTIGA_OID_COPROC_UID, false, optiga_id,
|
||||
sizeof(optiga_id), &optiga_id_size);
|
||||
if (OPTIGA_SUCCESS != ret) {
|
||||
vcp_println("ERROR optiga_get_data_object error %d for 0x%04x.", ret,
|
||||
OPTIGA_OID_COPROC_UID);
|
||||
cli_error(cli, CLI_ERROR, "optiga_get_data_object error %d for 0x%04x.",
|
||||
ret, OPTIGA_OID_COPROC_UID);
|
||||
return;
|
||||
}
|
||||
|
||||
vcp_print("OK ");
|
||||
vcp_println_hex(optiga_id, optiga_id_size);
|
||||
cli_ok_hexdata(cli, optiga_id, optiga_id_size);
|
||||
}
|
||||
|
||||
void cert_read(uint16_t oid) {
|
||||
if (!optiga_paired()) return;
|
||||
static void cert_read(cli_t* cli, uint16_t oid) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!optiga_paired(cli)) return;
|
||||
|
||||
static uint8_t cert[OPTIGA_MAX_CERT_SIZE] = {0};
|
||||
size_t cert_size = 0;
|
||||
optiga_result ret =
|
||||
optiga_get_data_object(oid, false, cert, sizeof(cert), &cert_size);
|
||||
if (OPTIGA_SUCCESS != ret) {
|
||||
vcp_println("ERROR optiga_get_data_object error %d for 0x%04x.", ret, oid);
|
||||
cli_error(cli, CLI_ERROR, "optiga_get_data_object error %d for 0x%04x.",
|
||||
ret, oid);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -370,7 +417,7 @@ void cert_read(uint16_t oid) {
|
||||
if (tls_identity_size + 3 > cert_size ||
|
||||
cert_chain_size + 3 > tls_identity_size ||
|
||||
first_cert_size > cert_chain_size) {
|
||||
vcp_println("ERROR invalid TLS identity in 0x%04x.", oid);
|
||||
cli_error(cli, CLI_ERROR, "invalid TLS identity in 0x%04x.", oid);
|
||||
return;
|
||||
}
|
||||
offset = 9;
|
||||
@ -378,33 +425,41 @@ void cert_read(uint16_t oid) {
|
||||
}
|
||||
|
||||
if (cert_size == 0) {
|
||||
vcp_println("ERROR no certificate in 0x%04x.", oid);
|
||||
cli_error(cli, CLI_ERROR, "no certificate in 0x%04x.", oid);
|
||||
return;
|
||||
}
|
||||
|
||||
vcp_print("OK ");
|
||||
vcp_println_hex(cert + offset, cert_size);
|
||||
cli_ok_hexdata(cli, cert + offset, cert_size);
|
||||
}
|
||||
|
||||
void cert_write(uint16_t oid, char *data) {
|
||||
if (!optiga_paired()) return;
|
||||
static void cert_write(cli_t* cli, uint16_t oid) {
|
||||
if (!optiga_paired(cli)) return;
|
||||
|
||||
// Enable writing to the certificate slot.
|
||||
optiga_metadata metadata = {0};
|
||||
metadata.change = OPTIGA_META_ACCESS_ALWAYS;
|
||||
set_metadata(oid, &metadata); // Ignore result.
|
||||
set_metadata(cli, oid, &metadata, false); // Ignore result.
|
||||
|
||||
size_t len = 0;
|
||||
uint8_t data_bytes[OPTIGA_MAX_CERT_SIZE];
|
||||
|
||||
int len = get_from_hex(data_bytes, sizeof(data_bytes), data);
|
||||
if (len < 0) {
|
||||
vcp_println("ERROR Hexadecimal decoding error %d.", len);
|
||||
if (!cli_arg_hex(cli, "hex-data", data_bytes, sizeof(data_bytes), &len)) {
|
||||
if (len == sizeof(data_bytes)) {
|
||||
cli_error(cli, CLI_ERROR, "Certificate too long.");
|
||||
} else {
|
||||
cli_error(cli, CLI_ERROR, "Hexadecimal decoding error.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 1) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
optiga_result ret = optiga_set_data_object(oid, false, data_bytes, len);
|
||||
if (OPTIGA_SUCCESS != ret) {
|
||||
vcp_println("ERROR optiga_set_data error %d for 0x%04x.", ret, oid);
|
||||
cli_error(cli, CLI_ERROR, "optiga_set_data error %d for 0x%04x.", ret, oid);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -414,20 +469,26 @@ void cert_write(uint16_t oid, char *data) {
|
||||
ret = optiga_get_data_object(oid, false, cert, sizeof(cert), &cert_size);
|
||||
if (OPTIGA_SUCCESS != ret || cert_size != len ||
|
||||
memcmp(data_bytes, cert, len) != 0) {
|
||||
vcp_println("ERROR optiga_get_data_object error %d for 0x%04x.", ret, oid);
|
||||
cli_error(cli, CLI_ERROR, "optiga_get_data_object error %d for 0x%04x.",
|
||||
ret, oid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (oid == OID_CERT_DEV && !check_device_cert_chain(cert, cert_size)) {
|
||||
if (oid == OID_CERT_DEV && !check_device_cert_chain(cli, cert, cert_size)) {
|
||||
// Error returned by check_device_cert_chain().
|
||||
return;
|
||||
}
|
||||
|
||||
vcp_println("OK");
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
void pubkey_read(uint16_t oid) {
|
||||
if (!optiga_paired()) return;
|
||||
static void pubkey_read(cli_t* cli, uint16_t oid) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!optiga_paired(cli)) return;
|
||||
|
||||
// Enable key agreement usage.
|
||||
|
||||
@ -435,7 +496,7 @@ void pubkey_read(uint16_t oid) {
|
||||
metadata.key_usage = OPTIGA_META_KEY_USE_KEYAGREE;
|
||||
metadata.execute = OPTIGA_META_ACCESS_ALWAYS;
|
||||
|
||||
if (!set_metadata(oid, &metadata)) {
|
||||
if (!set_metadata(cli, oid, &metadata, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -453,16 +514,15 @@ void pubkey_read(uint16_t oid) {
|
||||
optiga_calc_ssec(OPTIGA_CURVE_P256, oid, BASE_POINT, sizeof(BASE_POINT),
|
||||
public_key, sizeof(public_key), &public_key_size);
|
||||
if (OPTIGA_SUCCESS != ret) {
|
||||
vcp_println("ERROR optiga_calc_ssec error %d.", ret);
|
||||
cli_error(cli, CLI_ERROR, "optiga_calc_ssec error %d.", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
vcp_print("OK ");
|
||||
vcp_println_hex(public_key, public_key_size);
|
||||
cli_ok_hexdata(cli, public_key, public_key_size);
|
||||
}
|
||||
|
||||
void keyfido_write(char *data) {
|
||||
if (!optiga_paired()) return;
|
||||
static void prodtest_optiga_keyfido_write(cli_t* cli) {
|
||||
if (!optiga_paired(cli)) return;
|
||||
|
||||
const size_t EPH_PUB_KEY_SIZE = 33;
|
||||
const size_t PAYLOAD_SIZE = 32;
|
||||
@ -475,28 +535,38 @@ void keyfido_write(char *data) {
|
||||
metadata.key_usage = OPTIGA_META_KEY_USE_KEYAGREE;
|
||||
metadata.execute = OPTIGA_META_ACCESS_ALWAYS;
|
||||
|
||||
if (!set_metadata(OID_KEY_DEV, &metadata)) {
|
||||
if (!set_metadata(cli, OID_KEY_DEV, &metadata, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read encrypted FIDO attestation private key.
|
||||
|
||||
uint8_t data_bytes[EXPECTED_SIZE];
|
||||
int len = get_from_hex(data_bytes, sizeof(data_bytes), data);
|
||||
if (len < 0) {
|
||||
vcp_println("ERROR Hexadecimal decoding error %d.", len);
|
||||
size_t len = 0;
|
||||
|
||||
if (!cli_arg_hex(cli, "hex-data", data_bytes, sizeof(data_bytes), &len)) {
|
||||
if (len == sizeof(data_bytes)) {
|
||||
cli_error(cli, CLI_ERROR, "Key too long.");
|
||||
} else {
|
||||
cli_error(cli, CLI_ERROR, "Hexadecimal decoding error.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 1) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len != EXPECTED_SIZE) {
|
||||
vcp_println("ERROR Unexpected input length.");
|
||||
cli_error(cli, CLI_ERROR, "Unexpected input length.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Expand sender's ephemeral public key.
|
||||
uint8_t public_key[3 + 65] = {0x03, 0x42, 0x00};
|
||||
if (ecdsa_uncompress_pubkey(&nist256p1, data_bytes, &public_key[3]) != 1) {
|
||||
vcp_println("ERROR Failed to decode public key.");
|
||||
cli_error(cli, CLI_ERROR, "Failed to decode public key.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -508,7 +578,7 @@ void keyfido_write(char *data) {
|
||||
sizeof(secret), &secret_size);
|
||||
if (OPTIGA_SUCCESS != ret) {
|
||||
memzero(secret, sizeof(secret));
|
||||
vcp_println("ERROR optiga_calc_ssec error %d.", ret);
|
||||
cli_error(cli, CLI_ERROR, "optiga_calc_ssec error %d.", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -517,7 +587,7 @@ void keyfido_write(char *data) {
|
||||
aes_decrypt_ctx ctx = {0};
|
||||
AES_RETURN aes_ret = aes_decrypt_key256(secret, &ctx);
|
||||
if (EXIT_SUCCESS != aes_ret) {
|
||||
vcp_println("ERROR aes_decrypt_key256 error.");
|
||||
cli_error(cli, CLI_ERROR, "aes_decrypt_key256 error.");
|
||||
memzero(&ctx, sizeof(ctx));
|
||||
memzero(secret, sizeof(secret));
|
||||
return;
|
||||
@ -535,7 +605,7 @@ void keyfido_write(char *data) {
|
||||
memzero(secret, sizeof(secret));
|
||||
if (EXIT_SUCCESS != aes_ret) {
|
||||
memzero(fido_key, sizeof(fido_key));
|
||||
vcp_println("ERROR aes_cbc_decrypt error.");
|
||||
cli_error(cli, CLI_ERROR, "aes_cbc_decrypt error.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -543,7 +613,7 @@ void keyfido_write(char *data) {
|
||||
// write the FIDO key.
|
||||
memzero(&metadata, sizeof(metadata));
|
||||
metadata.data_type = OPTIGA_META_VALUE(OPTIGA_DATA_TYPE_TA);
|
||||
if (!set_metadata(OID_TRUST_ANCHOR, &metadata)) {
|
||||
if (!set_metadata(cli, OID_TRUST_ANCHOR, &metadata, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -551,7 +621,7 @@ void keyfido_write(char *data) {
|
||||
ret = optiga_set_trust_anchor();
|
||||
if (OPTIGA_SUCCESS != ret) {
|
||||
memzero(fido_key, sizeof(fido_key));
|
||||
vcp_println("ERROR optiga_set_trust_anchor error %d.", ret);
|
||||
cli_error(cli, CLI_ERROR, "optiga_set_trust_anchor error %d.", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -561,7 +631,7 @@ void keyfido_write(char *data) {
|
||||
metadata.change =
|
||||
OPTIGA_ACCESS_CONDITION(OPTIGA_ACCESS_COND_INT, OID_TRUST_ANCHOR);
|
||||
metadata.version = OPTIGA_META_VERSION_DEFAULT;
|
||||
if (!set_metadata(OID_KEY_FIDO, &metadata)) {
|
||||
if (!set_metadata(cli, OID_KEY_FIDO, &metadata, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -569,15 +639,20 @@ void keyfido_write(char *data) {
|
||||
ret = optiga_set_priv_key(OID_KEY_FIDO, fido_key);
|
||||
memzero(fido_key, sizeof(fido_key));
|
||||
if (OPTIGA_SUCCESS != ret) {
|
||||
vcp_println("ERROR optiga_set_priv_key error %d.", ret);
|
||||
cli_error(cli, CLI_ERROR, "optiga_set_priv_key error %d.", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
vcp_println("OK");
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
void sec_read(void) {
|
||||
if (!optiga_paired()) return;
|
||||
static void prodtest_optiga_counter_read(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!optiga_paired(cli)) return;
|
||||
|
||||
uint8_t sec = 0;
|
||||
size_t size = 0;
|
||||
@ -585,13 +660,12 @@ void sec_read(void) {
|
||||
optiga_result ret =
|
||||
optiga_get_data_object(OPTIGA_OID_SEC, false, &sec, sizeof(sec), &size);
|
||||
if (OPTIGA_SUCCESS != ret || sizeof(sec) != size) {
|
||||
vcp_println("ERROR optiga_get_data_object error %d for 0x%04x.", ret,
|
||||
OPTIGA_OID_SEC);
|
||||
cli_error(cli, CLI_ERROR, "optiga_get_data_object error %d for 0x%04x.",
|
||||
ret, OPTIGA_OID_SEC);
|
||||
return;
|
||||
}
|
||||
|
||||
vcp_print("OK ");
|
||||
vcp_println_hex(&sec, sizeof(sec));
|
||||
cli_ok_hexdata(cli, &sec, sizeof(sec));
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
@ -638,11 +712,12 @@ static bool get_extension_value(const uint8_t *extension_oid,
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool get_authority_key_digest(DER_ITEM *tbs_cert,
|
||||
static bool get_authority_key_digest(cli_t* cli, DER_ITEM* tbs_cert,
|
||||
const uint8_t** authority_key_digest) {
|
||||
DER_ITEM extensions = {0};
|
||||
if (!get_cert_extensions(tbs_cert, &extensions)) {
|
||||
vcp_println("ERROR get_authority_key_digest, extensions not found.");
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"get_authority_key_digest, extensions not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -651,8 +726,8 @@ static bool get_authority_key_digest(DER_ITEM *tbs_cert,
|
||||
if (!get_extension_value(OID_AUTHORITY_KEY_IDENTIFIER,
|
||||
sizeof(OID_AUTHORITY_KEY_IDENTIFIER), &extensions,
|
||||
&extension_value)) {
|
||||
vcp_println(
|
||||
"ERROR get_authority_key_digest, authority key identifier extension "
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"get_authority_key_digest, authority key identifier extension "
|
||||
"not found.");
|
||||
return false;
|
||||
}
|
||||
@ -661,8 +736,8 @@ static bool get_authority_key_digest(DER_ITEM *tbs_cert,
|
||||
DER_ITEM auth_key_id = {0};
|
||||
if (!der_read_item(&extension_value.buf, &auth_key_id) ||
|
||||
auth_key_id.id != DER_SEQUENCE) {
|
||||
vcp_println(
|
||||
"ERROR get_authority_key_digest, failed to open authority key "
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"get_authority_key_digest, failed to open authority key "
|
||||
"identifier extnValue.");
|
||||
return false;
|
||||
}
|
||||
@ -671,23 +746,24 @@ static bool get_authority_key_digest(DER_ITEM *tbs_cert,
|
||||
DER_ITEM key_id = {0};
|
||||
if (!der_read_item(&auth_key_id.buf, &key_id) ||
|
||||
key_id.id != DER_X509_KEY_IDENTIFIER) {
|
||||
vcp_println(
|
||||
"ERROR get_authority_key_digest, failed to find keyIdentifier field.");
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"get_authority_key_digest, failed to find keyIdentifier field.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return the pointer to the keyIdentifier data.
|
||||
if (buffer_remaining(&key_id.buf) != SHA1_DIGEST_LENGTH ||
|
||||
!buffer_ptr(&key_id.buf, authority_key_digest)) {
|
||||
vcp_println(
|
||||
"ERROR get_authority_key_digest, invalid length of keyIdentifier.");
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"get_authority_key_digest, invalid length of keyIdentifier.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) {
|
||||
static bool check_device_cert_chain(cli_t* cli, const uint8_t* chain,
|
||||
size_t chain_size) {
|
||||
// Checks the integrity of the device certificate chain to ensure that the
|
||||
// certificate data was not corrupted in transport and that the device
|
||||
// certificate belongs to this device. THIS IS NOT A FULL VERIFICATION OF THE
|
||||
@ -697,8 +773,7 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) {
|
||||
optiga_metadata metadata = {0};
|
||||
metadata.key_usage = KEY_USE_SIGN;
|
||||
metadata.execute = OPTIGA_META_ACCESS_ALWAYS;
|
||||
if (!set_metadata(OID_KEY_DEV, &metadata)) {
|
||||
vcp_println("ERROR check_device_cert_chain, set_metadata.");
|
||||
if (!set_metadata(cli, OID_KEY_DEV, &metadata, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -708,14 +783,14 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) {
|
||||
size_t der_sig_size = 0;
|
||||
if (optiga_calc_sign(OID_KEY_DEV, digest, sizeof(digest), &der_sig[2],
|
||||
sizeof(der_sig) - 2, &der_sig_size) != OPTIGA_SUCCESS) {
|
||||
vcp_println("ERROR check_device_cert_chain, optiga_calc_sign.");
|
||||
cli_error(cli, CLI_ERROR, "check_device_cert_chain, optiga_calc_sign.");
|
||||
return false;
|
||||
}
|
||||
der_sig[1] = der_sig_size;
|
||||
|
||||
uint8_t sig[64] = {0};
|
||||
if (ecdsa_sig_from_der(der_sig, der_sig_size + 2, sig) != 0) {
|
||||
vcp_println("ERROR check_device_cert_chain, ecdsa_sig_from_der.");
|
||||
cli_error(cli, CLI_ERROR, "check_device_cert_chain, ecdsa_sig_from_der.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -731,7 +806,8 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) {
|
||||
cert_count += 1;
|
||||
DER_ITEM cert = {0};
|
||||
if (!der_read_item(&chain_reader, &cert) || cert.id != DER_SEQUENCE) {
|
||||
vcp_println("ERROR check_device_cert_chain, der_read_item 1, cert %d.",
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"check_device_cert_chain, der_read_item 1, cert %d.",
|
||||
cert_count);
|
||||
return false;
|
||||
}
|
||||
@ -739,7 +815,8 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) {
|
||||
// Read the tbsCertificate.
|
||||
DER_ITEM tbs_cert = {0};
|
||||
if (!der_read_item(&cert.buf, &tbs_cert)) {
|
||||
vcp_println("ERROR check_device_cert_chain, der_read_item 2, cert %d.",
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"check_device_cert_chain, der_read_item 2, cert %d.",
|
||||
cert_count);
|
||||
return false;
|
||||
}
|
||||
@ -748,7 +825,8 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) {
|
||||
DER_ITEM pub_key_info = {0};
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
if (!der_read_item(&tbs_cert.buf, &pub_key_info)) {
|
||||
vcp_println("ERROR check_device_cert_chain, der_read_item 3, cert %d.",
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"check_device_cert_chain, der_read_item 3, cert %d.",
|
||||
cert_count);
|
||||
return false;
|
||||
}
|
||||
@ -760,7 +838,8 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) {
|
||||
const uint8_t* pub_key_bytes = NULL;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (!der_read_item(&pub_key_info.buf, &pub_key)) {
|
||||
vcp_println("ERROR check_device_cert_chain, der_read_item 4, cert %d.",
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"check_device_cert_chain, der_read_item 4, cert %d.",
|
||||
cert_count);
|
||||
return false;
|
||||
}
|
||||
@ -769,22 +848,23 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) {
|
||||
if (!buffer_get(&pub_key.buf, &unused_bits) ||
|
||||
buffer_remaining(&pub_key.buf) != 65 ||
|
||||
!buffer_ptr(&pub_key.buf, &pub_key_bytes)) {
|
||||
vcp_println("ERROR check_device_cert_chain, reading public key, cert %d.",
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"check_device_cert_chain, reading public key, cert %d.",
|
||||
cert_count);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the previous signature.
|
||||
if (ecdsa_verify_digest(&nist256p1, pub_key_bytes, sig, digest) != 0) {
|
||||
vcp_println(
|
||||
"ERROR check_device_cert_chain, ecdsa_verify_digest, cert %d.",
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"check_device_cert_chain, ecdsa_verify_digest, cert %d.",
|
||||
cert_count);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the authority key identifier from the last certificate.
|
||||
if (buffer_remaining(&chain_reader) == 0 &&
|
||||
!get_authority_key_digest(&tbs_cert, &authority_key_digest)) {
|
||||
!get_authority_key_digest(cli, &tbs_cert, &authority_key_digest)) {
|
||||
// Error returned by get_authority_key_digest().
|
||||
return false;
|
||||
}
|
||||
@ -798,8 +878,8 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) {
|
||||
sig_alg.buf.size != sizeof(ECDSA_WITH_SHA256) ||
|
||||
memcmp(ECDSA_WITH_SHA256, sig_alg.buf.data,
|
||||
sizeof(ECDSA_WITH_SHA256)) != 0) {
|
||||
vcp_println(
|
||||
"ERROR check_device_cert_chain, checking signatureAlgorithm, cert "
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"check_device_cert_chain, checking signatureAlgorithm, cert "
|
||||
"%d.",
|
||||
cert_count);
|
||||
return false;
|
||||
@ -809,8 +889,8 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) {
|
||||
DER_ITEM sig_val = {0};
|
||||
if (!der_read_item(&cert.buf, &sig_val) || sig_val.id != DER_BIT_STRING ||
|
||||
!buffer_get(&sig_val.buf, &unused_bits) || unused_bits != 0) {
|
||||
vcp_println(
|
||||
"ERROR check_device_cert_chain, reading signatureValue, cert %d.",
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"check_device_cert_chain, reading signatureValue, cert %d.",
|
||||
cert_count);
|
||||
return false;
|
||||
}
|
||||
@ -820,7 +900,8 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) {
|
||||
if (!buffer_ptr(&sig_val.buf, &sig_bytes) ||
|
||||
ecdsa_sig_from_der(sig_bytes, buffer_remaining(&sig_val.buf), sig) !=
|
||||
0) {
|
||||
vcp_println("ERROR check_device_cert_chain, ecdsa_sig_from_der, cert %d.",
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"check_device_cert_chain, ecdsa_sig_from_der, cert %d.",
|
||||
cert_count);
|
||||
return false;
|
||||
}
|
||||
@ -841,6 +922,112 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) {
|
||||
}
|
||||
}
|
||||
|
||||
vcp_println("ERROR check_device_cert_chain, ecdsa_verify_digest root.");
|
||||
cli_error(cli, CLI_ERROR,
|
||||
"check_device_cert_chain, ecdsa_verify_digest root.");
|
||||
return false;
|
||||
}
|
||||
|
||||
static void prodtest_optiga_certinf_read(cli_t* cli) {
|
||||
cert_read(cli, OID_CERT_INF);
|
||||
}
|
||||
|
||||
static void prodtest_optiga_certdev_read(cli_t* cli) {
|
||||
cert_read(cli, OID_CERT_DEV);
|
||||
}
|
||||
|
||||
static void prodtest_optiga_certdev_write(cli_t* cli) {
|
||||
cert_write(cli, OID_CERT_DEV);
|
||||
}
|
||||
|
||||
static void prodtest_optiga_certfido_read(cli_t* cli) {
|
||||
cert_read(cli, OID_CERT_FIDO);
|
||||
}
|
||||
|
||||
static void prodtest_optiga_certfido_write(cli_t* cli) {
|
||||
cert_write(cli, OID_CERT_FIDO);
|
||||
}
|
||||
|
||||
static void prodtest_optiga_keyfido_read(cli_t* cli) {
|
||||
pubkey_read(cli, OID_KEY_FIDO);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "optiga-id-read",
|
||||
.func = prodtest_optiga_id_read,
|
||||
.info = "Retrieve the unique ID of the Optiga chip",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "optiga-certinf-read",
|
||||
.func = prodtest_optiga_certinf_read,
|
||||
.info = "Read the X.509 certificate issued by Infineon",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "optiga-certdev-read",
|
||||
.func = prodtest_optiga_certdev_read,
|
||||
.info = "Read the device's X.509 certificate",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "optiga-certdev-write",
|
||||
.func = prodtest_optiga_certdev_write,
|
||||
.info = "Write the device's X.509 certificate",
|
||||
.args = "<hex-data>"
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "optiga-certfido-read",
|
||||
.func = prodtest_optiga_certfido_read,
|
||||
.info = "Read the X.509 certificate for the FIDO key",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "optiga-certfido-write",
|
||||
.func = prodtest_optiga_certfido_write,
|
||||
.info = "Write the X.509 certificate for the FIDO key",
|
||||
.args = "<hex-data>"
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "optiga-keyfido-read",
|
||||
.func = prodtest_optiga_keyfido_read,
|
||||
.info = "Read the x-coordinate of the FIDO public key.",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "optiga-keyfido-write",
|
||||
.func = prodtest_optiga_keyfido_write,
|
||||
.info = "Write the FIDO private key",
|
||||
.args = "<hex-data>"
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "optiga-lock",
|
||||
.func = prodtest_optiga_lock,
|
||||
.info = "Lock Optiga's data objects containing provisioning data",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "optiga-lock-check",
|
||||
.func = prodtest_optiga_lock_check,
|
||||
.info = "Check whether Optiga's data objects are locked",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "optiga-counter-read",
|
||||
.func = prodtest_optiga_counter_read,
|
||||
.info = "Read the Optiga security event counter",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
#endif // USE_OPTIGA
|
@ -17,18 +17,9 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef PRODTEST_OPTIGA_PRODTEST_H
|
||||
#define PRODTEST_OPTIGA_PRODTEST_H
|
||||
#pragma once
|
||||
|
||||
#include <trezor_types.h>
|
||||
|
||||
#define OID_CERT_INF (OPTIGA_OID_CERT + 0)
|
||||
#define OID_CERT_DEV (OPTIGA_OID_CERT + 1)
|
||||
#define OID_CERT_FIDO (OPTIGA_OID_CERT + 2)
|
||||
#define OID_KEY_DEV (OPTIGA_OID_ECC_KEY + 0)
|
||||
#define OID_KEY_FIDO (OPTIGA_OID_ECC_KEY + 2)
|
||||
#define OID_KEY_PAIRING OPTIGA_OID_PTFBIND_SECRET
|
||||
#define OID_TRUST_ANCHOR (OPTIGA_OID_CA_CERT + 0)
|
||||
#include <rtl/cli.h>
|
||||
|
||||
typedef enum {
|
||||
OPTIGA_LOCKED_TRUE,
|
||||
@ -36,16 +27,5 @@ typedef enum {
|
||||
OPTIGA_LOCKED_ERROR,
|
||||
} optiga_locked_status;
|
||||
|
||||
void pair_optiga(void);
|
||||
void optigaid_read(void);
|
||||
void cert_read(uint16_t oid);
|
||||
void cert_write(uint16_t oid, char *data);
|
||||
void keyfido_write(char *data);
|
||||
void pubkey_read(uint16_t oid);
|
||||
void optiga_lock(void);
|
||||
optiga_locked_status get_optiga_locked_status(void);
|
||||
void check_locked(void);
|
||||
void sec_read(void);
|
||||
bool check_device_cert_chain(const uint8_t *chain, size_t chain_size);
|
||||
|
||||
#endif
|
||||
void pair_optiga(cli_t* cli);
|
||||
optiga_locked_status get_optiga_locked_status(cli_t* cli);
|
157
core/embed/projects/prodtest/cmd/prodtest_otp_batch.c
Normal file
157
core/embed/projects/prodtest/cmd/prodtest_otp_batch.c
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <trezor_model.h>
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <rtl/cli.h>
|
||||
#include <util/flash_otp.h>
|
||||
|
||||
static void prodtest_otp_batch_read(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t block[FLASH_OTP_BLOCK_SIZE] = {0};
|
||||
|
||||
cli_trace(cli, "Reading device OTP memory...");
|
||||
|
||||
if (sectrue !=
|
||||
flash_otp_read(FLASH_OTP_BLOCK_BATCH, 0, block, sizeof(block))) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to read OTP memory.");
|
||||
}
|
||||
|
||||
char block_hex[FLASH_OTP_BLOCK_SIZE * 2 + 1];
|
||||
|
||||
if (!cstr_encode_hex(block_hex, sizeof(block_hex), block, sizeof(block))) {
|
||||
cli_error(cli, CLI_ERROR_FATAL, "Buffer too small.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Bytes read: %s", block_hex);
|
||||
|
||||
char block_text[FLASH_OTP_BLOCK_SIZE + 1] = {0};
|
||||
|
||||
for (size_t i = 0; i < sizeof(block); i++) {
|
||||
if (block[i] == 0xFF) {
|
||||
break;
|
||||
}
|
||||
block_text[i] = block[i];
|
||||
}
|
||||
|
||||
if (strlen(block_text) > 0) {
|
||||
cli_ok(cli, "%s", block_text);
|
||||
} else {
|
||||
cli_error(cli, CLI_ERROR_NODATA, "OTP block is empty.");
|
||||
}
|
||||
}
|
||||
|
||||
static void prodtest_otp_batch_write(cli_t* cli) {
|
||||
const char* data = cli_arg(cli, "text");
|
||||
|
||||
if (strlen(data) == 0 || strlen(data) > FLASH_OTP_BLOCK_SIZE - 1) {
|
||||
cli_error_arg(cli, "Expecting text (up to 31 characters).");
|
||||
return;
|
||||
}
|
||||
|
||||
#if PRODUCTION
|
||||
bool dry_run = false;
|
||||
#else
|
||||
bool dry_run = true;
|
||||
#endif
|
||||
|
||||
if (cli_has_nth_arg(cli, 1)) {
|
||||
const char* option = cli_nth_arg(cli, 1);
|
||||
if (strcmp(option, "--execute") == 0) {
|
||||
dry_run = false;
|
||||
} else if (strcmp(option, "--dry-run") == 0) {
|
||||
dry_run = true;
|
||||
} else {
|
||||
cli_error_arg(cli, "Expecting '--execute' or '--dry-run'.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 2) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dry_run) {
|
||||
cli_trace(cli, "");
|
||||
cli_trace(cli, "!!! It's a dry run, OTP will be left unchanged.");
|
||||
cli_trace(cli, "!!! Use '--execute' switch to write to OTP memory.");
|
||||
cli_trace(cli, "");
|
||||
}
|
||||
|
||||
uint8_t block[FLASH_OTP_BLOCK_SIZE] = {0};
|
||||
memcpy(block, data, MIN(sizeof(block) - 1, strlen(data)));
|
||||
|
||||
char block_hex[FLASH_OTP_BLOCK_SIZE * 2 + 1];
|
||||
if (!cstr_encode_hex(block_hex, sizeof(block_hex), block, sizeof(block))) {
|
||||
cli_error(cli, CLI_ERROR_FATAL, "Buffer too small.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (sectrue == flash_otp_is_locked(FLASH_OTP_BLOCK_BATCH)) {
|
||||
cli_error(cli, CLI_ERROR_LOCKED,
|
||||
"OTP block is locked and cannot be written again.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Writing device batch info into OTP memory...");
|
||||
cli_trace(cli, "Bytes written: %s", block_hex);
|
||||
|
||||
if (!dry_run) {
|
||||
if (sectrue !=
|
||||
flash_otp_write(FLASH_OTP_BLOCK_BATCH, 0, block, sizeof(block))) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to write OTP block.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cli_trace(cli, "Locking OTP block...");
|
||||
|
||||
if (!dry_run) {
|
||||
if (sectrue != flash_otp_lock(FLASH_OTP_BLOCK_BATCH)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to lock the OTP block.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Respond with an OK message
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "otp-batch-read",
|
||||
.func = prodtest_otp_batch_read,
|
||||
.info = "Read the device batch info from OTP memory",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "otp-batch-write",
|
||||
.func = prodtest_otp_batch_write,
|
||||
.info = "Write the device batch info into OTP memory",
|
||||
.args = "<text> [--execute | --dry-run]"
|
||||
);
|
179
core/embed/projects/prodtest/cmd/prodtest_otp_variant.c
Normal file
179
core/embed/projects/prodtest/cmd/prodtest_otp_variant.c
Normal file
@ -0,0 +1,179 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <trezor_model.h>
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <rtl/cli.h>
|
||||
#include <util/flash_otp.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "prodtest_optiga.h"
|
||||
|
||||
static void prodtest_otp_variant_read(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t block[FLASH_OTP_BLOCK_SIZE] = {0};
|
||||
|
||||
cli_trace(cli, "Reading device OTP memory...");
|
||||
|
||||
if (sectrue !=
|
||||
flash_otp_read(FLASH_OTP_BLOCK_DEVICE_VARIANT, 0, block, sizeof(block))) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to read OTP memory.");
|
||||
return;
|
||||
}
|
||||
|
||||
char block_hex[FLASH_OTP_BLOCK_SIZE * 2 + 1];
|
||||
|
||||
if (!cstr_encode_hex(block_hex, sizeof(block_hex), block, sizeof(block))) {
|
||||
cli_error(cli, CLI_ERROR_FATAL, "Buffer too small.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Bytes read: %s", block_hex);
|
||||
|
||||
char block_text[FLASH_OTP_BLOCK_SIZE * 4 + 1] = {0};
|
||||
|
||||
// Make a list of integers separated by spaces
|
||||
char* dst = block_text;
|
||||
for (int i = 0; i < sizeof(block); i++) {
|
||||
if (i != 0) {
|
||||
*dst++ = ' ';
|
||||
}
|
||||
itoa(block[i], dst, 10);
|
||||
dst += strlen(dst);
|
||||
}
|
||||
|
||||
cli_ok(cli, "%s", block_text);
|
||||
}
|
||||
|
||||
static void prodtest_otp_variant_write(cli_t* cli) {
|
||||
uint8_t block[FLASH_OTP_BLOCK_SIZE] = {0};
|
||||
|
||||
#if PRODUCTION
|
||||
bool dry_run = false;
|
||||
#else
|
||||
bool dry_run = true;
|
||||
#endif
|
||||
|
||||
int arg_idx = 0;
|
||||
int val_count = 0;
|
||||
|
||||
block[val_count++] = 0x01; // Always 1
|
||||
|
||||
while (arg_idx < cli_arg_count(cli)) {
|
||||
const char* arg = cli_nth_arg(cli, arg_idx++);
|
||||
uint32_t val = 0;
|
||||
|
||||
if (strcmp(arg, "--execute") == 0) {
|
||||
dry_run = false;
|
||||
} else if (strcmp(arg, "--dry-run") == 0) {
|
||||
dry_run = true;
|
||||
} else if (!cstr_parse_uint32(arg, 0, &val) || val > 255) {
|
||||
cli_error_arg(cli, "Expecting values in range 0-255.");
|
||||
return;
|
||||
} else if (val_count >= sizeof(block)) {
|
||||
cli_error_arg(cli, "Too many values, %d is the maximum.",
|
||||
sizeof(block) - 1);
|
||||
return;
|
||||
} else {
|
||||
block[val_count++] = val;
|
||||
}
|
||||
}
|
||||
|
||||
if (val_count == 0) {
|
||||
cli_error_arg(cli, "Expecting at least one value.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (dry_run) {
|
||||
cli_trace(cli, "");
|
||||
cli_trace(cli, "!!! It's a dry run, OTP will be left unchanged.");
|
||||
cli_trace(cli, "!!! Use '--execute' switch to write to OTP memory.");
|
||||
cli_trace(cli, "");
|
||||
}
|
||||
|
||||
#ifdef USE_OPTIGA
|
||||
optiga_locked_status optiga_status = get_optiga_locked_status(cli);
|
||||
|
||||
if (optiga_status == OPTIGA_LOCKED_FALSE) {
|
||||
cli_error(cli, CLI_ERROR, "Optiga not locked");
|
||||
return;
|
||||
}
|
||||
|
||||
if (optiga_status != OPTIGA_LOCKED_TRUE) {
|
||||
// Error reported by get_optiga_locked_status().
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (sectrue == flash_otp_is_locked(FLASH_OTP_BLOCK_BATCH)) {
|
||||
cli_error(cli, CLI_ERROR_LOCKED,
|
||||
"OTP block is locked and cannot be written again.");
|
||||
return;
|
||||
}
|
||||
|
||||
char block_hex[FLASH_OTP_BLOCK_SIZE * 2 + 1];
|
||||
if (!cstr_encode_hex(block_hex, sizeof(block_hex), block, sizeof(block))) {
|
||||
cli_error(cli, CLI_ERROR_FATAL, "Buffer too small.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Writing device batch info into OTP memory...");
|
||||
cli_trace(cli, "Bytes written: %s", block_hex);
|
||||
|
||||
if (!dry_run) {
|
||||
if (sectrue != flash_otp_write(FLASH_OTP_BLOCK_DEVICE_VARIANT, 0, block,
|
||||
sizeof(block))) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to write OTP block.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cli_trace(cli, "Locking OTP block...");
|
||||
|
||||
if (!dry_run) {
|
||||
if (sectrue != flash_otp_lock(FLASH_OTP_BLOCK_DEVICE_VARIANT)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to lock the OTP block.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Respond with an OK message
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "otp-variant-read",
|
||||
.func = prodtest_otp_variant_read,
|
||||
.info = "Read the device variant info from OTP memory",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "otp-variant-write",
|
||||
.func = prodtest_otp_variant_write,
|
||||
.info = "Write the device variant info into OTP memory",
|
||||
.args = "<values...> [--execute | --dry-run]"
|
||||
);
|
@ -17,17 +17,25 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef PRODTEST_COMMON_H
|
||||
#define PRODTEST_COMMON_H
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <trezor_types.h>
|
||||
#include <rtl/cli.h>
|
||||
|
||||
enum { VCP_IFACE = 0x00 };
|
||||
static void prodtest_ping(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
void vcp_puts(const char *s, size_t len);
|
||||
void vcp_print(const char *fmt, ...);
|
||||
void vcp_println(const char *fmt, ...);
|
||||
void vcp_println_hex(const uint8_t *data, uint16_t len);
|
||||
int get_from_hex(uint8_t *buf, uint16_t buf_len, const char *hex);
|
||||
// Respond with an OK message
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
#endif
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "ping",
|
||||
.func = prodtest_ping,
|
||||
.info = "Send a ping to the device",
|
||||
.args = ""
|
||||
);
|
245
core/embed/projects/prodtest/cmd/prodtest_pmic.c
Normal file
245
core/embed/projects/prodtest/cmd/prodtest_pmic.c
Normal file
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_POWERCTL
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <rtl/cli.h>
|
||||
#include <sys/systick.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "../../../sys/powerctl/npm1300/npm1300.h"
|
||||
|
||||
static void prodtest_pmic_init(cli_t* cli) {
|
||||
cli_trace(cli, "Initializing the NPM1300 driver...");
|
||||
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
npm1300_deinit();
|
||||
|
||||
if (!npm1300_init()) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to initialize NPM1300 driver.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_pmic_charge_enable(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Enabling battery charging @ %dmA...",
|
||||
npm1300_get_charging_limit());
|
||||
|
||||
if (!npm1300_set_charging(true)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to enable battery charging.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_pmic_charge_disable(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Disabling battery charging...");
|
||||
|
||||
if (!npm1300_set_charging(false)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to disable battery charging.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_pmic_charge_set_limit(cli_t* cli) {
|
||||
uint32_t i_charge = 0;
|
||||
|
||||
if (!cli_arg_uint32(cli, "limit", &i_charge) ||
|
||||
i_charge < NPM1300_CHARGING_LIMIT_MIN ||
|
||||
i_charge > NPM1300_CHARGING_LIMIT_MAX) {
|
||||
cli_error_arg(cli, "Expecting charging limit in range %d-%d mA.",
|
||||
NPM1300_CHARGING_LIMIT_MIN, NPM1300_CHARGING_LIMIT_MAX);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 1) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Setting battery charging limit to %d mA...", i_charge);
|
||||
|
||||
if (!npm1300_set_charging_limit(i_charge)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to set battery charging limit.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_pmic_buck_set_mode(cli_t* cli) {
|
||||
npm1300_buck_mode_t buck_mode = NPM1300_BUCK_MODE_AUTO;
|
||||
|
||||
const char* mode = cli_arg(cli, "mode");
|
||||
if (strcmp(mode, "pwm") == 0) {
|
||||
buck_mode = NPM1300_BUCK_MODE_PWM;
|
||||
} else if (strcmp(mode, "pfm") == 0) {
|
||||
buck_mode = NPM1300_BUCK_MODE_PFM;
|
||||
} else if (strcmp(mode, "auto") == 0) {
|
||||
buck_mode = NPM1300_BUCK_MODE_AUTO;
|
||||
} else {
|
||||
cli_error_arg(cli, "Buck converter mode expected (pwm, pfm or auto).");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 1) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Setting the buck converter mode...");
|
||||
|
||||
if (!npm1300_set_buck_mode(buck_mode)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to set buck converter mode.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_pmic_report(cli_t* cli) {
|
||||
uint32_t count = 1;
|
||||
uint32_t period = 1000;
|
||||
|
||||
if (cli_has_arg(cli, "count") && !cli_arg_uint32(cli, "count", &count)) {
|
||||
cli_error_arg(cli, "Expecting count of measurements.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_has_arg(cli, "period") && !cli_arg_uint32(cli, "period", &period)) {
|
||||
cli_error_arg(cli, "Expecting period in milliseconds.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 2) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli,
|
||||
" time vbat ibat ntc vsys die bat buck mode");
|
||||
|
||||
uint32_t ticks = hal_ticks_ms();
|
||||
|
||||
while (count-- > 0) {
|
||||
npm1300_report_t report;
|
||||
|
||||
if (!npm1300_measure_sync(&report)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to get NPM1300 report.");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* state = "IDLE";
|
||||
|
||||
bool ibat_discharging = ((report.ibat_meas_status >> 2) & 0x03) == 1;
|
||||
bool ibat_charging = ((report.ibat_meas_status >> 2) & 0x03) == 3;
|
||||
|
||||
if (ibat_discharging) {
|
||||
state = "DISCHARGING";
|
||||
} else if (ibat_charging) {
|
||||
state = "CHARGING";
|
||||
}
|
||||
|
||||
cli_progress(
|
||||
cli, "%09d %d.%03d %d.%03d %d.%03d %d.%03d %d.%03d 0x%02X 0x%02X %s",
|
||||
ticks, (int)report.vbat, (int)(report.vbat * 1000) % 1000,
|
||||
(int)report.ibat, (int)abs(report.ibat * 1000) % 1000,
|
||||
(int)report.ntc_temp, (int)abs(report.ntc_temp * 1000) % 1000,
|
||||
(int)report.vsys, (int)(report.vsys * 1000) % 1000,
|
||||
(int)report.die_temp, (int)abs(report.die_temp * 1000) % 1000,
|
||||
report.ibat_meas_status, report.buck_status, state);
|
||||
|
||||
do {
|
||||
if (cli_aborted(cli)) {
|
||||
return;
|
||||
}
|
||||
} while (!ticks_expired(ticks + period));
|
||||
|
||||
ticks += period;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "pmic-init",
|
||||
.func = prodtest_pmic_init,
|
||||
.info = "Initialize the PMIC driver",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "pmic-charge-enable",
|
||||
.func = prodtest_pmic_charge_enable,
|
||||
.info = "Enable battery charging",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "pmic-charge-disable",
|
||||
.func = prodtest_pmic_charge_disable,
|
||||
.info = "Disable battery charging",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "pmic-charge-set-limit",
|
||||
.func = prodtest_pmic_charge_set_limit,
|
||||
.info = "Set the battery charging limit",
|
||||
.args = "<limit>"
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "pmic-buck-set-mode",
|
||||
.func = prodtest_pmic_buck_set_mode,
|
||||
.info = "Set the buck converter mode",
|
||||
.args = "<mode>"
|
||||
)
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "pmic-report",
|
||||
.func = prodtest_pmic_report,
|
||||
.info = "Retrieve PMIC report",
|
||||
.args = "[<count>] [<period>]"
|
||||
);
|
||||
|
||||
#endif // USE_POWERCTL
|
55
core/embed/projects/prodtest/cmd/prodtest_powerctl.c
Normal file
55
core/embed/projects/prodtest/cmd/prodtest_powerctl.c
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_POWERCTL
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <rtl/cli.h>
|
||||
#include <sys/powerctl.h>
|
||||
#include <sys/systick.h>
|
||||
|
||||
static void prodtest_powerctl_suspend(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Suspending the device to low-power mode...");
|
||||
cli_trace(cli, "Press the POWER button to resume.");
|
||||
systick_delay_ms(1000);
|
||||
|
||||
powerctl_suspend();
|
||||
|
||||
systick_delay_ms(1500);
|
||||
cli_trace(cli, "Resumed to active mode.");
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "powerctl-suspend",
|
||||
.func = prodtest_powerctl_suspend,
|
||||
.info = "Suspend the device to low-power mode",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
#endif // USE_POWERCTL
|
95
core/embed/projects/prodtest/cmd/prodtest_prodtest.c
Normal file
95
core/embed/projects/prodtest/cmd/prodtest_prodtest.c
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <trezor_model.h>
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <gfx/fonts.h>
|
||||
#include <gfx/gfx_draw.h>
|
||||
#include <io/display.h>
|
||||
#include <rtl/cli.h>
|
||||
#include <sys/bootutils.h>
|
||||
#include <sys/mpu.h>
|
||||
#include <sys/systick.h>
|
||||
#include <util/fwutils.h>
|
||||
|
||||
#include <version.h>
|
||||
|
||||
static gfx_text_attr_t bold = {
|
||||
.font = FONT_BOLD,
|
||||
.fg_color = COLOR_WHITE,
|
||||
.bg_color = COLOR_BLACK,
|
||||
};
|
||||
|
||||
static void prodtest_prodtest_intro(cli_t* cli) {
|
||||
cli_trace(cli, "Welcome to Trezor %s Production Test Firmware v%d.%d.%d.",
|
||||
MODEL_NAME, VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
|
||||
cli_trace(cli, "");
|
||||
cli_trace(cli, "Type 'help' to view all available commands.");
|
||||
cli_trace(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_prodtest_version(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_ok(cli, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
|
||||
}
|
||||
|
||||
static void prodtest_prodtest_wipe(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Invalidating the production test firmware header...");
|
||||
firmware_invalidate_header();
|
||||
|
||||
gfx_clear();
|
||||
gfx_offset_t pos = gfx_offset(DISPLAY_RESX / 2, DISPLAY_RESY / 2 + 10);
|
||||
gfx_draw_text(pos, "WIPED", -1, &bold, GFX_ALIGN_CENTER);
|
||||
display_refresh();
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "$intro",
|
||||
.func = prodtest_prodtest_intro,
|
||||
.info = "",
|
||||
.args = "",
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "prodtest-version",
|
||||
.func = prodtest_prodtest_version,
|
||||
.info = "Retrieve the production test firmware version",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "prodtest-wipe",
|
||||
.func = prodtest_prodtest_wipe,
|
||||
.info = "Wipe the production test firmware",
|
||||
.args = ""
|
||||
);
|
48
core/embed/projects/prodtest/cmd/prodtest_reboot.c
Normal file
48
core/embed/projects/prodtest/cmd/prodtest_reboot.c
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <rtl/cli.h>
|
||||
#include <sys/bootutils.h>
|
||||
#include <sys/systick.h>
|
||||
|
||||
static void prodtest_reboot(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
|
||||
// Reboot with a delay to allow the response to be sent
|
||||
systick_delay_ms(1000);
|
||||
reboot_device();
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "reboot",
|
||||
.func = prodtest_reboot,
|
||||
.info = "Reboot the device",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
|
70
core/embed/projects/prodtest/cmd/prodtest_rgbled.c
Normal file
70
core/embed/projects/prodtest/cmd/prodtest_rgbled.c
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_RGB_LED
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <io/rgb_led.h>
|
||||
#include <rtl/cli.h>
|
||||
|
||||
static void prodtest_rgbled_set(cli_t* cli) {
|
||||
uint32_t r = 0;
|
||||
uint32_t g = 0;
|
||||
uint32_t b = 0;
|
||||
|
||||
if (!cli_arg_uint32(cli, "r", &r) || r > 255) {
|
||||
cli_error_arg(cli, "Expecting red value in range 0-255.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cli_arg_uint32(cli, "g", &g) || g > 255) {
|
||||
cli_error_arg(cli, "Expecting green value in range 0-255.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cli_arg_uint32(cli, "b", &b) || b > 255) {
|
||||
cli_error_arg(cli, "Expecting blue value in range 0-255.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 3) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Setting the RGB LED color to [%d, %d, %d]...", r, g, b);
|
||||
|
||||
uint32_t rgb = (r << 16) | (g << 8) | b;
|
||||
|
||||
rgb_led_set_color(rgb);
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "rgbled-set",
|
||||
.func = prodtest_rgbled_set,
|
||||
.info = "Set the RGB LED color",
|
||||
.args = "<r> <g> <b>"
|
||||
);
|
||||
|
||||
#endif // USE_RGB_LED
|
61
core/embed/projects/prodtest/cmd/prodtest_sbu.c
Normal file
61
core/embed/projects/prodtest/cmd/prodtest_sbu.c
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_SBU
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <io/sbu.h>
|
||||
#include <rtl/cli.h>
|
||||
|
||||
static void prodtest_sbu_set(cli_t* cli) {
|
||||
uint32_t sbu1 = 0;
|
||||
uint32_t sbu2 = 0;
|
||||
|
||||
if (!cli_arg_uint32(cli, "sbu1", &sbu1) || sbu1 > 1) {
|
||||
cli_error_arg(cli, "Expecting logical level (0 or 1).");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cli_arg_uint32(cli, "sbu2", &sbu2) || sbu2 > 1) {
|
||||
cli_error_arg(cli, "Expecting logical level (0 or 1).");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 2) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Setting SBU1 to %d and SBU2 to %d...", sbu1, sbu2);
|
||||
sbu_set(sectrue * sbu1, sectrue * sbu2);
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "sbu-set",
|
||||
.func = prodtest_sbu_set,
|
||||
.info = "Set the SBU pins' levels",
|
||||
.args = "<sbu1> <sbu2>"
|
||||
);
|
||||
|
||||
#endif // USE_SBU
|
106
core/embed/projects/prodtest/cmd/prodtest_sdcard.c
Normal file
106
core/embed/projects/prodtest/cmd/prodtest_sdcard.c
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_SD_CARD
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <io/sdcard.h>
|
||||
#include <rtl/cli.h>
|
||||
#include <sys/systick.h>
|
||||
|
||||
static void prodtest_sdcard_test(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
#define BLOCK_SIZE (32 * 1024)
|
||||
static uint32_t buf1[BLOCK_SIZE / sizeof(uint32_t)];
|
||||
static uint32_t buf2[BLOCK_SIZE / sizeof(uint32_t)];
|
||||
|
||||
bool low_speed = false;
|
||||
|
||||
#ifndef TREZOR_MODEL_T3T1
|
||||
if (sectrue != sdcard_is_present()) {
|
||||
cli_trace(cli, "The inserted SD card is required.");
|
||||
cli_error(cli, "no-card", "");
|
||||
return;
|
||||
}
|
||||
#else
|
||||
low_speed = true;
|
||||
#endif
|
||||
|
||||
cli_trace(cli, "Powering on the SD card...");
|
||||
|
||||
if (sectrue != sdcard_power_on_unchecked(low_speed)) {
|
||||
cli_error(cli, CLI_ERROR, "SD card power on sequence failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Reading data from the SD card...");
|
||||
|
||||
if (sectrue != sdcard_read_blocks(buf1, 0, BLOCK_SIZE / SDCARD_BLOCK_SIZE)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to read data from SD card.");
|
||||
goto power_off;
|
||||
}
|
||||
|
||||
for (int j = 1; j <= 2; j++) {
|
||||
cli_trace(cli, "Writing data to the SD card (attempt #%d)...", j);
|
||||
|
||||
for (int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); i++) {
|
||||
buf1[i] ^= 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
if (sectrue !=
|
||||
sdcard_write_blocks(buf1, 0, BLOCK_SIZE / SDCARD_BLOCK_SIZE)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to write data from the SD card.");
|
||||
goto power_off;
|
||||
}
|
||||
|
||||
systick_delay_ms(1000);
|
||||
|
||||
if (sectrue !=
|
||||
sdcard_read_blocks(buf2, 0, BLOCK_SIZE / SDCARD_BLOCK_SIZE)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to read data from SD card.");
|
||||
goto power_off;
|
||||
}
|
||||
|
||||
if (0 != memcmp(buf1, buf2, sizeof(buf1))) {
|
||||
cli_error(cli, CLI_ERROR, "Data mismatch after writing to SD card.");
|
||||
goto power_off;
|
||||
}
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
|
||||
power_off:
|
||||
sdcard_power_off();
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "sdcard-test",
|
||||
.func = prodtest_sdcard_test,
|
||||
.info = "Test the SD card interface",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
#endif // USE_SD_CARD
|
418
core/embed/projects/prodtest/cmd/prodtest_touch.c
Normal file
418
core/embed/projects/prodtest/cmd/prodtest_touch.c
Normal file
@ -0,0 +1,418 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_TOUCH
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <gfx/fonts.h>
|
||||
#include <gfx/gfx_draw.h>
|
||||
#include <io/display.h>
|
||||
#include <io/touch.h>
|
||||
#include <rtl/cli.h>
|
||||
#include <sys/systick.h>
|
||||
|
||||
const static gfx_text_attr_t bold = {
|
||||
.font = FONT_BOLD,
|
||||
.fg_color = COLOR_WHITE,
|
||||
.bg_color = COLOR_BLACK,
|
||||
};
|
||||
|
||||
static bool ensure_touch_init(cli_t* cli) {
|
||||
cli_trace(cli, "Initializing the touch controller...");
|
||||
if (sectrue != touch_init()) {
|
||||
cli_error(cli, CLI_ERROR, "Cannot initialize touch controller.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prodtest_touch_version(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ensure_touch_init(cli)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Reading the touch controller version...");
|
||||
uint8_t version = touch_get_version();
|
||||
|
||||
cli_ok(cli, "%d", version);
|
||||
|
||||
touch_deinit();
|
||||
}
|
||||
|
||||
static bool touch_click_timeout(cli_t* cli, uint32_t* event, uint32_t timeout) {
|
||||
uint32_t deadline = ticks_timeout(timeout);
|
||||
uint32_t ev = 0;
|
||||
|
||||
while (touch_get_event()) {
|
||||
if (ticks_expired(deadline) || cli_aborted(cli)) return false;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Waiting for a touch for %d ms...", timeout);
|
||||
|
||||
while ((touch_get_event() & TOUCH_START) == 0) {
|
||||
if (ticks_expired(deadline) || cli_aborted(cli)) return false;
|
||||
}
|
||||
|
||||
while (((ev = touch_get_event()) & TOUCH_END) == 0) {
|
||||
if (ticks_expired(deadline) || cli_aborted(cli)) return false;
|
||||
}
|
||||
|
||||
while (touch_get_event()) {
|
||||
if (ticks_expired(deadline) || cli_aborted(cli)) return false;
|
||||
}
|
||||
|
||||
*event = ev;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prodtest_touch_test(cli_t* cli) {
|
||||
uint32_t position = 0;
|
||||
uint32_t timeout = 0;
|
||||
|
||||
if (!cli_arg_uint32(cli, "position", &position)) {
|
||||
cli_error_arg(cli, "Expecting position (0, 1, 2 or 3).");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cli_arg_uint32(cli, "timeout", &timeout)) {
|
||||
cli_error_arg(cli, "Expecting timeout in milliseconds.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 2) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ensure_touch_init(cli)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int width = DISPLAY_RESX / 2;
|
||||
const int height = DISPLAY_RESY / 2;
|
||||
|
||||
gfx_clear();
|
||||
switch (position) {
|
||||
case 1:
|
||||
gfx_draw_bar(gfx_rect_wh(0, 0, width, height), COLOR_WHITE);
|
||||
break;
|
||||
case 2:
|
||||
gfx_draw_bar(gfx_rect_wh(width, 0, width, height), COLOR_WHITE);
|
||||
break;
|
||||
case 3:
|
||||
gfx_draw_bar(gfx_rect_wh(width, height, width, height), COLOR_WHITE);
|
||||
break;
|
||||
default:
|
||||
gfx_draw_bar(gfx_rect_wh(0, height, width, height), COLOR_WHITE);
|
||||
break;
|
||||
}
|
||||
|
||||
display_refresh();
|
||||
|
||||
uint32_t event = 0;
|
||||
if (touch_click_timeout(cli, &event, timeout)) {
|
||||
uint16_t x = touch_unpack_x(event);
|
||||
uint16_t y = touch_unpack_y(event);
|
||||
cli_ok(cli, "%d %d", x, y);
|
||||
} else {
|
||||
if (!cli_aborted(cli)) {
|
||||
cli_error(cli, CLI_ERROR_TIMEOUT, "");
|
||||
}
|
||||
}
|
||||
|
||||
touch_deinit();
|
||||
|
||||
gfx_clear();
|
||||
display_refresh();
|
||||
}
|
||||
|
||||
static void prodtest_touch_test_custom(cli_t* cli) {
|
||||
uint32_t x = 0;
|
||||
uint32_t y = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t timeout = 0;
|
||||
|
||||
if (!cli_arg_uint32(cli, "x", &x)) {
|
||||
cli_error_arg(cli, "Expecting x coordinate.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cli_arg_uint32(cli, "y", &y)) {
|
||||
cli_error_arg(cli, "Expecting y coordinate.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cli_arg_uint32(cli, "width", &width)) {
|
||||
cli_error_arg(cli, "Expecting rectangle width.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cli_arg_uint32(cli, "height", &height)) {
|
||||
cli_error_arg(cli, "Expecting rectangle height.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cli_arg_uint32(cli, "timeout", &timeout)) {
|
||||
cli_error_arg(cli, "Expecting timeout in milliseconds.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 5) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ensure_touch_init(cli)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Drawing a rectangle at [%d, %d] with size [%d x %d]...", x, y,
|
||||
width, height);
|
||||
|
||||
gfx_clear();
|
||||
gfx_draw_bar(gfx_rect_wh(x, y, width, height), COLOR_WHITE);
|
||||
display_refresh();
|
||||
|
||||
uint32_t expire_time = ticks_timeout(timeout);
|
||||
|
||||
cli_trace(cli, "Waiting for a touch for %d ms...", timeout);
|
||||
|
||||
while (true) {
|
||||
if (ticks_expired(expire_time)) {
|
||||
cli_error(cli, CLI_ERROR_TIMEOUT, "");
|
||||
break;
|
||||
}
|
||||
|
||||
if (cli_aborted(cli)) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t touch_event = touch_get_event();
|
||||
if (touch_event != 0) {
|
||||
uint16_t touch_x = touch_unpack_x(touch_event);
|
||||
uint16_t touch_y = touch_unpack_y(touch_event);
|
||||
uint32_t ticks = systick_ms();
|
||||
|
||||
if (touch_event & TOUCH_START) {
|
||||
cli_progress(cli, "start %d %d %d", touch_x, touch_y, ticks);
|
||||
}
|
||||
if (touch_event & TOUCH_MOVE) {
|
||||
cli_progress(cli, "move %d %d %d", touch_x, touch_y, ticks);
|
||||
}
|
||||
if (touch_event & TOUCH_END) {
|
||||
cli_progress(cli, "end %d %d %d", touch_x, touch_y, ticks);
|
||||
cli_ok(cli, "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
touch_deinit();
|
||||
|
||||
gfx_clear();
|
||||
display_refresh();
|
||||
}
|
||||
|
||||
static void prodtest_touch_test_idle(cli_t* cli) {
|
||||
uint32_t timeout = 0;
|
||||
|
||||
if (!cli_arg_uint32(cli, "timeout", &timeout)) {
|
||||
cli_error_arg(cli, "Expecting timeout in milliseconds.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 1) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
gfx_clear();
|
||||
gfx_offset_t pos = gfx_offset(DISPLAY_RESX / 2, DISPLAY_RESY / 2);
|
||||
gfx_draw_text(pos, "DON'T TOUCH", -1, &bold, GFX_ALIGN_CENTER);
|
||||
display_refresh();
|
||||
|
||||
if (!ensure_touch_init(cli)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Don't touch the screen for %d ms...", timeout);
|
||||
|
||||
uint32_t deadline = ticks_timeout(timeout);
|
||||
bool activity = false;
|
||||
|
||||
while (!ticks_expired(deadline) && !activity && !cli_aborted(cli)) {
|
||||
activity = (sectrue == touch_activity());
|
||||
};
|
||||
|
||||
if (cli_aborted(cli)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (activity) {
|
||||
cli_error(cli, CLI_ERROR, "Unexpected activity detected.");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
|
||||
cleanup:
|
||||
touch_deinit();
|
||||
gfx_clear();
|
||||
display_refresh();
|
||||
}
|
||||
|
||||
static void prodtest_touch_test_power(cli_t* cli) {
|
||||
uint32_t timeout = 0;
|
||||
|
||||
if (!cli_arg_uint32(cli, "timeout", &timeout)) {
|
||||
cli_error_arg(cli, "Expecting timeout in milliseconds.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 1) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
gfx_clear();
|
||||
gfx_offset_t pos = gfx_offset(DISPLAY_RESX / 2, DISPLAY_RESY / 2);
|
||||
gfx_draw_text(pos, "MEASURING", -1, &bold, GFX_ALIGN_CENTER);
|
||||
display_refresh();
|
||||
|
||||
cli_trace(cli, "Setting touch controller power for %d ms...", timeout);
|
||||
|
||||
touch_power_set(true);
|
||||
|
||||
uint32_t deadline = ticks_timeout(timeout);
|
||||
while (!ticks_expired(deadline)) {
|
||||
systick_delay_ms(1);
|
||||
if (cli_aborted(cli)) {
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
|
||||
cleanup:
|
||||
touch_power_set(false);
|
||||
gfx_clear();
|
||||
display_refresh();
|
||||
}
|
||||
|
||||
static void prodtest_touch_test_sensitivity(cli_t* cli) {
|
||||
uint32_t sensitivity = 0;
|
||||
|
||||
if (!cli_arg_uint32(cli, "sensitivity", &sensitivity) || sensitivity > 255) {
|
||||
cli_error_arg(cli, "Expecting sensitivity level in range 0-255.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 1) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ensure_touch_init(cli)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Setting touch controller sensitivity to %d...", sensitivity);
|
||||
touch_set_sensitivity(sensitivity);
|
||||
|
||||
cli_trace(cli, "Running touch controller test...");
|
||||
cli_trace(cli, "Press CTRL+C for exit.");
|
||||
|
||||
gfx_clear();
|
||||
display_refresh();
|
||||
|
||||
for (;;) {
|
||||
uint32_t evt = touch_get_event();
|
||||
if (evt & TOUCH_START || evt & TOUCH_MOVE) {
|
||||
int x = touch_unpack_x(evt);
|
||||
int y = touch_unpack_y(evt);
|
||||
gfx_clear();
|
||||
gfx_draw_bar(gfx_rect_wh(x - 48, y - 48, 96, 96), COLOR_WHITE);
|
||||
display_refresh();
|
||||
} else if (evt & TOUCH_END) {
|
||||
gfx_clear();
|
||||
display_refresh();
|
||||
}
|
||||
if (cli_aborted(cli)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
touch_deinit();
|
||||
|
||||
gfx_clear();
|
||||
display_refresh();
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "touch-version",
|
||||
.func = prodtest_touch_version,
|
||||
.info = "Retrieve the touch controller version",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "touch-test",
|
||||
.func = prodtest_touch_test,
|
||||
.info = "Test the touch controller",
|
||||
.args = "<position> <timeout>"
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "touch-test-custom",
|
||||
.func = prodtest_touch_test_custom,
|
||||
.info = "Test the touch controller with custom parameters",
|
||||
.args = "<x> <y> <width> <height> <timeout>"
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "touch-test-idle",
|
||||
.func = prodtest_touch_test_idle,
|
||||
.info = "Test the touch controller in idle mode",
|
||||
.args = "<timeout>"
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "touch-test-power",
|
||||
.func = prodtest_touch_test_power,
|
||||
.info = "Test the touch controller's power consumption",
|
||||
.args = "<timeout>"
|
||||
)
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "touch-test-sensitivity",
|
||||
.func = prodtest_touch_test_sensitivity,
|
||||
.info = "Test the touch controller sensitivity",
|
||||
.args = "<sensitivity>"
|
||||
)
|
||||
|
||||
#endif // USE_TOUCH
|
289
core/embed/projects/prodtest/cmd/prodtest_wpc.c
Normal file
289
core/embed/projects/prodtest/cmd/prodtest_wpc.c
Normal file
@ -0,0 +1,289 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_POWERCTL
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <rtl/cli.h>
|
||||
#include <sys/systick.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "../../../sys/powerctl/stwlc38/stwlc38.h"
|
||||
|
||||
static void prodtest_wpc_init(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Initializing the WPC driver...");
|
||||
|
||||
stwlc38_deinit();
|
||||
|
||||
if (!stwlc38_init()) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to initialize STWLC38.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_wpc_enable(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Enabling STWLC38...");
|
||||
|
||||
if (!stwlc38_enable(true)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to enable STWLC38.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_wpc_disable(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Disabling STWLC38...");
|
||||
|
||||
if (!stwlc38_enable(false)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to disable STWLC38.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_wpc_vout_enable(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Enabling STWLC38 output...");
|
||||
|
||||
if (!stwlc38_enable_vout(true)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to enable STWLC38 output.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_wpc_vout_disable(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Disabling STWLC38 output...");
|
||||
|
||||
if (!stwlc38_enable_vout(false)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to disable STWLC38 output.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_wpc_report(cli_t* cli) {
|
||||
uint32_t count = 1;
|
||||
uint32_t period = 1000;
|
||||
|
||||
if (cli_has_arg(cli, "count") && !cli_arg_uint32(cli, "count", &count)) {
|
||||
cli_error_arg(cli, "Expecting count of measurements.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_has_arg(cli, "timeout") && !cli_arg_uint32(cli, "timeout", &period)) {
|
||||
cli_error_arg(cli, "Expecting period in milliseconds.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_arg_count(cli) > 2) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(
|
||||
cli,
|
||||
" time ready vout_ready vrect vout icur tmeas opfreq ntc");
|
||||
|
||||
uint32_t ticks = hal_ticks_ms();
|
||||
|
||||
while (count-- > 0) {
|
||||
stwlc38_report_t report;
|
||||
|
||||
if (!stwlc38_get_report(&report)) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to get STWLC38 report.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_progress(cli, "%09d %d %d %d.%03d %d.%03d %d.%03d %d.%03d %d %d.%03d",
|
||||
ticks, report.ready ? 1 : 0, report.vout_ready ? 1 : 0,
|
||||
(int)report.vrect, (int)abs(report.vrect * 1000) % 1000,
|
||||
(int)report.vout, (int)(report.vout * 1000) % 1000,
|
||||
(int)report.icur, (int)abs(report.icur * 1000) % 1000,
|
||||
(int)report.tmeas, (int)abs(report.tmeas * 1000) % 1000,
|
||||
report.opfreq, (int)report.ntc,
|
||||
(int)abs(report.ntc * 1000) % 1000);
|
||||
|
||||
do {
|
||||
if (cli_aborted(cli)) {
|
||||
return;
|
||||
}
|
||||
} while (!ticks_expired(ticks + period));
|
||||
|
||||
ticks += period;
|
||||
}
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_wpc_info(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
stwlc38_chip_info_t chip_info;
|
||||
|
||||
cli_trace(cli, "Reading STWLC38 info...");
|
||||
if (!stwlc38_read_chip_info(&chip_info)) {
|
||||
cli_error(cli, CLI_ERROR, "Cannot read STWLC38 info.");
|
||||
return;
|
||||
}
|
||||
|
||||
char device_id[sizeof(chip_info.device_id) * 2 + 1];
|
||||
|
||||
if (!cstr_encode_hex(device_id, sizeof(device_id), chip_info.device_id,
|
||||
sizeof(chip_info.device_id))) {
|
||||
cli_error(cli, CLI_ERROR_FATAL, "Buffer too small.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "chip_id 0x%d ", chip_info.chip_id);
|
||||
cli_trace(cli, "chip_rev 0x%d ", chip_info.chip_rev);
|
||||
cli_trace(cli, "cust_id 0x%d ", chip_info.cust_id);
|
||||
cli_trace(cli, "rom_id 0x%X ", chip_info.rom_id);
|
||||
cli_trace(cli, "patch_id 0x%X ", chip_info.patch_id);
|
||||
cli_trace(cli, "cfg_id 0x%X ", chip_info.cfg_id);
|
||||
cli_trace(cli, "pe_id 0x%X ", chip_info.pe_id);
|
||||
cli_trace(cli, "op_mode 0x%X ", chip_info.op_mode);
|
||||
cli_trace(cli, "device_id %s", device_id);
|
||||
cli_trace(cli, "");
|
||||
cli_trace(cli, "sys_err 0x%X ", chip_info.sys_err);
|
||||
cli_trace(cli, " core_hard_fault: 0x%X ", chip_info.core_hard_fault);
|
||||
cli_trace(cli, " nvm_ip_err: 0x%X ", chip_info.nvm_ip_err);
|
||||
cli_trace(cli, " nvm_boot_err: 0x%X ", chip_info.nvm_boot_err);
|
||||
cli_trace(cli, " nvm_pe_error: 0x%X ", chip_info.nvm_pe_error);
|
||||
cli_trace(cli, " nvm_config_err: 0x%X ", chip_info.nvm_config_err);
|
||||
cli_trace(cli, " nvm_patch_err: 0x%X ", chip_info.nvm_patch_err);
|
||||
cli_trace(cli, " nvm_prod_info_err: 0x%X ", chip_info.nvm_prod_info_err);
|
||||
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
static void prodtest_wpc_update(cli_t* cli) {
|
||||
if (cli_arg_count(cli) > 0) {
|
||||
cli_error_arg_count(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "Updating STWLC38...");
|
||||
|
||||
uint32_t update_time = systick_ms();
|
||||
bool status = stwlc38_patch_and_config();
|
||||
update_time = systick_ms() - update_time;
|
||||
|
||||
if (status == false) {
|
||||
cli_error(cli, CLI_ERROR, "Failed to update STWLC38.");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_trace(cli, "WPC update completed {%d ms}", update_time);
|
||||
cli_ok(cli, "");
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "wpc-init",
|
||||
.func = prodtest_wpc_init,
|
||||
.info = "Initialize the WPC driver",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "wpc-enable",
|
||||
.func = prodtest_wpc_enable,
|
||||
.info = "Enable the WPC chip",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "wpc-disable",
|
||||
.func = prodtest_wpc_disable,
|
||||
.info = "Disable the WPC chip",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "wpc-out-enable",
|
||||
.func = prodtest_wpc_vout_enable,
|
||||
.info = "Enable WPC output",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "wpc-out-disable",
|
||||
.func = prodtest_wpc_vout_disable,
|
||||
.info = "Disable WPC output",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "wpc-report",
|
||||
.func = prodtest_wpc_report,
|
||||
.info = "Retrieve WPC report",
|
||||
.args = "[<count>] [<timeout>]"
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "wpc-info",
|
||||
.func = prodtest_wpc_info,
|
||||
.info = "Retrieve WPC chip information",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
PRODTEST_CLI_CMD(
|
||||
.name = "wpc-update",
|
||||
.func = prodtest_wpc_update,
|
||||
.info = "Update WPC firmware & configuration",
|
||||
.args = ""
|
||||
);
|
||||
|
||||
#endif // USE_POWERCTL
|
File diff suppressed because it is too large
Load Diff
@ -1,102 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "prodtest_common.h"
|
||||
#include <io/usb.h>
|
||||
#include <rtl/mini_printf.h>
|
||||
|
||||
void vcp_puts(const char *s, size_t len) {
|
||||
int r = usb_vcp_write_blocking(VCP_IFACE, (const uint8_t *)s, len, -1);
|
||||
(void)r;
|
||||
}
|
||||
|
||||
void vcp_print(const char *fmt, ...) {
|
||||
static char buf[128];
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
int r = mini_vsnprintf(buf, sizeof(buf), fmt, va);
|
||||
va_end(va);
|
||||
vcp_puts(buf, r);
|
||||
}
|
||||
|
||||
void vcp_println(const char *fmt, ...) {
|
||||
static char buf[128];
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
int r = mini_vsnprintf(buf, sizeof(buf), fmt, va);
|
||||
va_end(va);
|
||||
vcp_puts(buf, r);
|
||||
vcp_puts("\r\n", 2);
|
||||
}
|
||||
|
||||
void vcp_println_hex(const uint8_t *data, uint16_t len) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
vcp_print("%02X", data[i]);
|
||||
}
|
||||
vcp_puts("\r\n", 2);
|
||||
}
|
||||
|
||||
static uint16_t get_byte_from_hex(const char **hex) {
|
||||
uint8_t result = 0;
|
||||
|
||||
// Skip whitespace.
|
||||
while (**hex == ' ') {
|
||||
*hex += 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
result <<= 4;
|
||||
char c = **hex;
|
||||
if (c >= '0' && c <= '9') {
|
||||
result |= c - '0';
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
result |= c - 'A' + 10;
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
result |= c - 'a' + 10;
|
||||
} else if (c == '\0') {
|
||||
return 0x100;
|
||||
} else {
|
||||
return 0xFFFF;
|
||||
}
|
||||
*hex += 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int get_from_hex(uint8_t *buf, uint16_t buf_len, const char *hex) {
|
||||
int len = 0;
|
||||
uint16_t b = get_byte_from_hex(&hex);
|
||||
for (len = 0; len < buf_len && b <= 0xff; ++len) {
|
||||
buf[len] = b;
|
||||
b = get_byte_from_hex(&hex);
|
||||
}
|
||||
|
||||
if (b == 0x100) {
|
||||
// Success.
|
||||
return len;
|
||||
}
|
||||
|
||||
if (b > 0xff) {
|
||||
// Non-hexadecimal character.
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Buffer too small.
|
||||
return -2;
|
||||
}
|
@ -3,8 +3,8 @@
|
||||
#include "model_version.h"
|
||||
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 2
|
||||
#define VERSION_PATCH 13
|
||||
#define VERSION_MINOR 3
|
||||
#define VERSION_PATCH 0
|
||||
#define VERSION_BUILD 0
|
||||
|
||||
#define FIX_VERSION_MAJOR 0
|
||||
|
671
core/embed/rtl/cli.c
Normal file
671
core/embed/rtl/cli.c
Normal file
@ -0,0 +1,671 @@
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <rtl/cli.h>
|
||||
#include <rtl/mini_printf.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#define ESC_COLOR_GREEN "\e[32m"
|
||||
#define ESC_COLOR_RED "\e[31m"
|
||||
#define ESC_COLOR_GRAY "\e[37m"
|
||||
#define ESC_COLOR_RESET "\e[39m"
|
||||
|
||||
bool cli_init(cli_t* cli, cli_read_cb_t read, cli_write_cb_t write,
|
||||
void* callback_context) {
|
||||
memset(cli, 0, sizeof(cli_t));
|
||||
cli->read = read;
|
||||
cli->write = write;
|
||||
cli->callback_context = callback_context;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cli_set_commands(cli_t* cli, const cli_command_t* cmd_array,
|
||||
size_t cmd_count) {
|
||||
cli->cmd_array = cmd_array;
|
||||
cli->cmd_count = cmd_count;
|
||||
}
|
||||
|
||||
static void cli_vprintf(cli_t* cli, const char* format, va_list args) {
|
||||
char buffer[CLI_LINE_BUFFER_SIZE];
|
||||
mini_vsnprintf(buffer, sizeof(buffer), format, args);
|
||||
cli->write(cli->callback_context, buffer, strlen(buffer));
|
||||
}
|
||||
|
||||
static void cli_printf(cli_t* cli, const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
cli_vprintf(cli, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void cli_vtrace(cli_t* cli, const char* format, va_list args) {
|
||||
if (cli->interactive) {
|
||||
cli_printf(cli, ESC_COLOR_GRAY);
|
||||
}
|
||||
|
||||
cli_printf(cli, "#");
|
||||
|
||||
if (cli->interactive) {
|
||||
cli_printf(cli, ESC_COLOR_RESET);
|
||||
}
|
||||
|
||||
if (format != NULL && format[0] != '\0') {
|
||||
// Print the formatted message
|
||||
cli_printf(cli, " ");
|
||||
cli_vprintf(cli, format, args);
|
||||
}
|
||||
|
||||
cli_printf(cli, "\r\n");
|
||||
}
|
||||
|
||||
void cli_trace(cli_t* cli, const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
cli_vtrace(cli, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void cli_ok(cli_t* cli, const char* format, ...) {
|
||||
va_list args;
|
||||
|
||||
if (cli->interactive) {
|
||||
cli_printf(cli, ESC_COLOR_GREEN);
|
||||
}
|
||||
|
||||
cli_printf(cli, "OK");
|
||||
|
||||
if (cli->interactive) {
|
||||
cli_printf(cli, ESC_COLOR_RESET);
|
||||
}
|
||||
|
||||
if (format != NULL && format[0] != '\0') {
|
||||
// Print the formatted message
|
||||
cli_printf(cli, " ");
|
||||
va_start(args, format);
|
||||
cli_vprintf(cli, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
cli_printf(cli, "\r\n");
|
||||
|
||||
cli->final_status = true;
|
||||
}
|
||||
|
||||
// Write OK response with hex-encoded data
|
||||
void cli_ok_hexdata(cli_t* cli, const void* data, size_t size) {
|
||||
if (cli->interactive) {
|
||||
cli_printf(cli, ESC_COLOR_GREEN);
|
||||
}
|
||||
|
||||
cli_printf(cli, "OK");
|
||||
|
||||
if (cli->interactive) {
|
||||
cli_printf(cli, ESC_COLOR_RESET);
|
||||
}
|
||||
|
||||
if (size > 0) {
|
||||
cli_printf(cli, " ");
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
cli_printf(cli, "%02X", ((uint8_t*)data)[i]);
|
||||
}
|
||||
}
|
||||
cli_printf(cli, "\r\n");
|
||||
|
||||
cli->final_status = true;
|
||||
}
|
||||
|
||||
static void cli_verror(cli_t* cli, const char* code, const char* format,
|
||||
va_list args) {
|
||||
if (cli->interactive) {
|
||||
cli_printf(cli, ESC_COLOR_RED);
|
||||
}
|
||||
|
||||
cli_printf(cli, "ERROR");
|
||||
|
||||
if (cli->interactive) {
|
||||
cli_printf(cli, ESC_COLOR_RESET);
|
||||
}
|
||||
|
||||
cli_printf(cli, " %s", code);
|
||||
|
||||
if (format != NULL && format[0] != '\0') {
|
||||
cli_printf(cli, " \"");
|
||||
// Print the formatted message
|
||||
cli_vprintf(cli, format, args);
|
||||
cli_printf(cli, "\"");
|
||||
}
|
||||
|
||||
cli_printf(cli, "\r\n");
|
||||
|
||||
cli->final_status = true;
|
||||
}
|
||||
|
||||
void cli_error(cli_t* cli, const char* code, const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
cli_verror(cli, code, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void cli_error_arg(cli_t* cli, const char* format, ...) {
|
||||
if (cli->interactive && cli->current_cmd != NULL) {
|
||||
const cli_command_t* cmd = cli->current_cmd;
|
||||
if (cmd->args != NULL) {
|
||||
cli_trace(cli, "USAGE: %s %s", cmd->name, cmd->args);
|
||||
} else {
|
||||
cli_trace(cli, "USAGE: %s", cmd->name);
|
||||
}
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
cli_verror(cli, CLI_ERROR_INVALID_ARG, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void cli_error_arg_count(cli_t* cli) {
|
||||
cli_error_arg(cli, "Unexpected trailing input.");
|
||||
}
|
||||
|
||||
void cli_progress(cli_t* cli, const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
cli_printf(cli, "PROGRESS");
|
||||
|
||||
if (format != NULL && format[0] != '\0') {
|
||||
cli_printf(cli, " ");
|
||||
// Print the formatted message
|
||||
cli_vprintf(cli, format, args);
|
||||
}
|
||||
|
||||
cli_printf(cli, "\r\n");
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void cli_abort(cli_t* cli) { cli->aborted = true; }
|
||||
|
||||
bool cli_aborted(cli_t* cli) { return cli->aborted; }
|
||||
|
||||
// Finds a command record by name
|
||||
//
|
||||
// Returns NULL if the command is not found
|
||||
static const cli_command_t* cli_find_command(cli_t* cli, const char* cmd) {
|
||||
for (size_t i = 0; i < cli->cmd_count; i++) {
|
||||
if (strcmp(cmd, cli->cmd_array[i].name) == 0) {
|
||||
return &cli->cmd_array[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define INDEX_ADD(index, offset) \
|
||||
(((index) + CLI_HISTORY_DEPTH + (offset)) % CLI_HISTORY_DEPTH)
|
||||
|
||||
static void cli_history_add(cli_t* cli, const char* line) {
|
||||
size_t line_len = strlen(line);
|
||||
if (line_len == 0 || line_len >= CLI_HISTORY_LINE_SIZE) {
|
||||
// Skip empty or too long lines
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < CLI_HISTORY_DEPTH; i++) {
|
||||
if (strcmp(cli->history[i], line) == 0) {
|
||||
// Duplicate line => Move it to the top
|
||||
for (; i != INDEX_ADD(cli->history_head, -1); i = INDEX_ADD(i, 1)) {
|
||||
strcpy(cli->history[i], cli->history[INDEX_ADD(i, 1)]);
|
||||
}
|
||||
strcpy(cli->history[i], line);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new line to the history
|
||||
strcpy(cli->history[cli->history_head], line);
|
||||
cli->history_head = (cli->history_head + 1) % CLI_HISTORY_DEPTH;
|
||||
}
|
||||
|
||||
// Searches the history for the previous command that starts with the prefix
|
||||
// provided in the `line` buffer.
|
||||
//
|
||||
// `idx` is the index of the current command in the history
|
||||
//
|
||||
// Returns NULL if there are no more commands
|
||||
static const char* cli_history_rev(cli_t* cli, int* idx, char* line,
|
||||
int prefix) {
|
||||
for (int i = *idx + 1; i <= CLI_HISTORY_DEPTH; i++) {
|
||||
const char* hist_line = cli->history[INDEX_ADD(cli->history_head, -i)];
|
||||
if (*hist_line == '\0') break;
|
||||
if (strlen(hist_line) >= prefix && strncmp(hist_line, line, prefix) == 0) {
|
||||
*idx = i;
|
||||
return hist_line;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Searches the history for the next command that starts with the prefix
|
||||
// provided in the `line` buffer.
|
||||
//
|
||||
// `idx` is the index of the current command in the history
|
||||
//
|
||||
// Returns NULL if there are no more commands
|
||||
static const char* cli_history_fwd(cli_t* cli, int* idx, char* line,
|
||||
int prefix) {
|
||||
for (int i = *idx - 1; i > 0; i--) {
|
||||
const char* hist_line = cli->history[INDEX_ADD(cli->history_head, -i)];
|
||||
if (strlen(hist_line) >= prefix && strncmp(hist_line, line, prefix) == 0) {
|
||||
*idx = i;
|
||||
return hist_line;
|
||||
}
|
||||
}
|
||||
*idx = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define ESC_SEQ(ch) (0x200 + (ch))
|
||||
|
||||
// Reads a character from the console input and return it.
|
||||
// Comple escape sequences translates into ESC_SEQ values
|
||||
// - ESC[<letter> => ESC_SEQ(letter), e.g. ESC[A => ESC_SEQ('A')
|
||||
// - ESC[<number>~ => ESC_SEQ(number) , e.g. ESC[3~ => ESC_SEQ(3)
|
||||
static int cli_readch(cli_t* cli) {
|
||||
int esc_len = 0; // >0 if we are in the middle of an escape sequence
|
||||
int esc_code = 0; // numeric code of the escape sequence
|
||||
|
||||
for (;;) {
|
||||
char ch;
|
||||
cli->read(cli->callback_context, &ch, 1);
|
||||
|
||||
if (ch == '\e') {
|
||||
// Escape sequence start
|
||||
esc_len = 1;
|
||||
} else if (esc_len == 1) {
|
||||
if (ch == '\e') {
|
||||
return 'e';
|
||||
} else if (ch == '[') {
|
||||
// Control sequence introducer
|
||||
esc_len = 2;
|
||||
esc_code = 0;
|
||||
} else {
|
||||
esc_len = 0;
|
||||
}
|
||||
} else if (esc_len == 2 && ch >= 'A' && ch <= 'Z') {
|
||||
// XTERM sequences - ESC[<letter>
|
||||
return ESC_SEQ(ch);
|
||||
} else if (esc_len >= 2 && ch >= '0' && ch <= '9') {
|
||||
// VT sequences - ESC[<number>~
|
||||
esc_code = esc_code * 10 + (ch - '0');
|
||||
esc_len++;
|
||||
} else if (esc_len >= 3 && ch == '~') {
|
||||
// End of VT sequence
|
||||
return ESC_SEQ(esc_code);
|
||||
} else if (esc_len >= 3) {
|
||||
// Invalid VT sequence
|
||||
esc_len = 0;
|
||||
} else {
|
||||
// Non-escape character
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finds the next character that can be used for autocomplete.
|
||||
// Returns '\0' if there are no more characters.
|
||||
static char cli_autocomplete(cli_t* cli, const char* prefix) {
|
||||
char next_char = '\0';
|
||||
size_t prefix_len = strlen(prefix);
|
||||
for (size_t i = 0; i < cli->cmd_count; i++) {
|
||||
const char* cmd = cli->cmd_array[i].name;
|
||||
if (cstr_starts_with(cmd, prefix)) {
|
||||
char ch = cmd[prefix_len];
|
||||
if (next_char == '\0') {
|
||||
next_char = ch;
|
||||
} else if (ch != next_char) {
|
||||
return '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
return next_char;
|
||||
}
|
||||
|
||||
// Reads a line from the console input and stores it in the `cli->line` buffer
|
||||
//
|
||||
// Returns false if the input line is too long
|
||||
static bool cli_readln(cli_t* cli) {
|
||||
char* buf = cli->line_buffer;
|
||||
int len = 0; // number of characters in the buffer (excluding '\0')
|
||||
int cursor = 0; // cursor position in the buffer
|
||||
|
||||
int hist_idx = 0;
|
||||
int hist_prefix = 0;
|
||||
|
||||
buf[0] = '\0';
|
||||
|
||||
for (;;) {
|
||||
int ch = cli_readch(cli);
|
||||
|
||||
switch (ch) {
|
||||
case ESC_SEQ('A'): // ESC[A
|
||||
// Up arrow - search history backwards
|
||||
if (hist_idx == 0) {
|
||||
hist_prefix = len;
|
||||
}
|
||||
const char* hist_line =
|
||||
cli_history_rev(cli, &hist_idx, buf, hist_prefix);
|
||||
if (hist_line != NULL) {
|
||||
if (cursor > 0) {
|
||||
// Move the cursor to the beginning of the line
|
||||
cli_printf(cli, "\e[%dD", cursor);
|
||||
}
|
||||
// Replace original text
|
||||
strcpy(buf, hist_line);
|
||||
len = cursor = strlen(buf);
|
||||
cli_printf(cli, "%s\e[K", buf);
|
||||
}
|
||||
continue;
|
||||
|
||||
case ESC_SEQ('B'): // ESC[B
|
||||
// Down arrow - search history forwards
|
||||
if (hist_idx > 0) {
|
||||
const char* hist_line =
|
||||
cli_history_fwd(cli, &hist_idx, buf, hist_prefix);
|
||||
if (hist_line != NULL) {
|
||||
if (cursor > 0) {
|
||||
// Move the cursor to the beginning of the line
|
||||
cli_printf(cli, "\e[%dD", cursor);
|
||||
}
|
||||
// Replace original text
|
||||
strcpy(buf, hist_line);
|
||||
len = cursor = strlen(buf);
|
||||
cli_printf(cli, "%s\e[K", buf);
|
||||
} else {
|
||||
if (cursor > hist_prefix) {
|
||||
cli_printf(cli, "\e[%dD", cursor - hist_prefix);
|
||||
}
|
||||
cli_printf(cli, "\e[K");
|
||||
len = cursor = hist_prefix;
|
||||
buf[len] = '\0';
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reset the history index, if the user types something else
|
||||
hist_idx = 0;
|
||||
|
||||
switch (ch) {
|
||||
case ESC_SEQ('C'): // ESC[C
|
||||
// Right arrow
|
||||
if (cursor < len) {
|
||||
if (cli->interactive) {
|
||||
cli_printf(cli, "\e[C");
|
||||
}
|
||||
cursor++;
|
||||
}
|
||||
break;
|
||||
|
||||
case ESC_SEQ('D'): // ESC[D
|
||||
// Left arrow
|
||||
if (cursor > 0) {
|
||||
if (cli->interactive) {
|
||||
cli_printf(cli, "\e[D");
|
||||
}
|
||||
cursor--;
|
||||
}
|
||||
break;
|
||||
|
||||
case '\b':
|
||||
case 0x7F:
|
||||
// backspace => delete last character
|
||||
if (cursor == 0) break;
|
||||
if (cli->interactive) {
|
||||
// Move the cursor left
|
||||
cli_printf(cli, "\e[D");
|
||||
}
|
||||
--cursor;
|
||||
// do not break, fall through
|
||||
|
||||
case ESC_SEQ(3): // ESC[3~
|
||||
// Delete
|
||||
if (cursor < len) {
|
||||
// Delete the character at the cursor
|
||||
memmove(&buf[cursor], &buf[cursor + 1], len - cursor);
|
||||
--len;
|
||||
if (cli->interactive) {
|
||||
// Print the rest of the line and move the cursor back
|
||||
cli_printf(cli, "%s \b", &buf[cursor]);
|
||||
if (cursor < len) {
|
||||
cli_printf(cli, "\e[%dD", len - cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
case '\n':
|
||||
// end of line
|
||||
if (cli->interactive) {
|
||||
cli_printf(cli, "\r\n");
|
||||
}
|
||||
return len < CLI_LINE_BUFFER_SIZE;
|
||||
|
||||
case '\t':
|
||||
// tab => autocomplete
|
||||
if (cli->interactive && len == cursor) {
|
||||
char ch;
|
||||
while ((ch = cli_autocomplete(cli, buf)) != '\0') {
|
||||
if (len < CLI_LINE_BUFFER_SIZE - 1) {
|
||||
cli_printf(cli, "%c", ch);
|
||||
buf[len++] = ch;
|
||||
buf[len] = '\0';
|
||||
cursor++;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (ch >= 0x20 && ch <= 0x7E) {
|
||||
// Printable character
|
||||
if (len < CLI_LINE_BUFFER_SIZE - 1) {
|
||||
// Insert the character at the cursor
|
||||
++len;
|
||||
memmove(&buf[cursor + 1], &buf[cursor], len - cursor);
|
||||
buf[cursor] = ch;
|
||||
// Print new character and the rest of the line
|
||||
if (cli->interactive) {
|
||||
cli_printf(cli, "%s", &buf[cursor]);
|
||||
}
|
||||
++cursor;
|
||||
if (cli->interactive && cursor < len) {
|
||||
// Move the cursor back
|
||||
cli_printf(cli, "\e[%dD", len - cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Splits the command line into arguments
|
||||
// Returns false if there are too many arguments
|
||||
static const char* cstr_token(char** str) {
|
||||
char* p = *str;
|
||||
// Skip leading whitespace
|
||||
p = (char*)cstr_skip_whitespace(p);
|
||||
// Start of token
|
||||
const char* token = p;
|
||||
// Find the end of the token
|
||||
while (*p != '\0' && !isspace((unsigned char)*p)) {
|
||||
++p;
|
||||
}
|
||||
// Terminate the token
|
||||
if (*p != '\0') {
|
||||
*p++ = '\0';
|
||||
}
|
||||
*str = p;
|
||||
return token;
|
||||
}
|
||||
|
||||
static bool cli_split_args(cli_t* cli) {
|
||||
char* buf = cli->line_buffer;
|
||||
|
||||
cli->cmd_name = cstr_token(&buf);
|
||||
cli->args_count = 0;
|
||||
|
||||
while (*buf != '\0' && cli->args_count < CLI_MAX_ARGS) {
|
||||
const char* arg = cstr_token(&buf);
|
||||
if (*arg != '\0') {
|
||||
cli->args[cli->args_count++] = arg;
|
||||
}
|
||||
}
|
||||
|
||||
return *cstr_skip_whitespace(buf) == '\0';
|
||||
}
|
||||
|
||||
void cli_run_loop(cli_t* cli) {
|
||||
while (true) {
|
||||
if (cli->interactive) {
|
||||
if (cli->final_status) {
|
||||
// Finalize the last command with an empty line
|
||||
cli_printf(cli, "\r\n");
|
||||
}
|
||||
// Print the prompt
|
||||
cli_printf(cli, "> ");
|
||||
}
|
||||
|
||||
cli->final_status = false;
|
||||
cli->aborted = false;
|
||||
|
||||
// Read the next line
|
||||
if (!cli_readln(cli)) {
|
||||
cli_error(cli, CLI_ERROR_FATAL, "Input line too long.");
|
||||
continue;
|
||||
}
|
||||
|
||||
cli_history_add(cli, cli->line_buffer);
|
||||
|
||||
// Split command line into arguments
|
||||
if (!cli_split_args(cli)) {
|
||||
cli_error(cli, CLI_ERROR_FATAL, "Too many arguments.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Empty line?
|
||||
if (*cli->cmd_name == '\0') {
|
||||
// Switch to interactive mode if two empty lines are entered
|
||||
if (++cli->empty_lines >= 2 && !cli->interactive) {
|
||||
cli->interactive = true;
|
||||
// Print the welcome message
|
||||
const cli_command_t* cmd = cli_find_command(cli, "$intro");
|
||||
if (cmd != NULL) {
|
||||
cmd->func(cli);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
cli->empty_lines = 0;
|
||||
|
||||
// Find the command handler
|
||||
cli->current_cmd = cli_find_command(cli, cli->cmd_name);
|
||||
|
||||
if (cli->current_cmd == NULL) {
|
||||
cli_error(cli, CLI_ERROR_INVALID_CMD, "Invalid command '%s', try 'help'.",
|
||||
cli->cmd_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Call the command handler
|
||||
cli->current_cmd->func(cli);
|
||||
|
||||
if (!cli->final_status) {
|
||||
// Command handler hasn't send final status
|
||||
if (cli->aborted) {
|
||||
cli_error(cli, CLI_ERROR_ABORT, "");
|
||||
} else {
|
||||
cli_error(cli, CLI_ERROR_FATAL,
|
||||
"Command handler didn't finish properly.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return position of the argument with the given name in
|
||||
// the command definition.
|
||||
//
|
||||
// Returns -1 if the argument is not present.
|
||||
static int find_arg(const cli_command_t* cmd, const char* name) {
|
||||
if (cmd->args == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* p = cmd->args;
|
||||
int index = 0;
|
||||
|
||||
while (*p != '\0') {
|
||||
// Skip '<' or '[>'
|
||||
while (*p != '\0' && (*p == ' ' || *p == '<' || *p == '[')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
// Extract argument name
|
||||
const char* s = p;
|
||||
while (*p != '\0' && (*p != '>' && *p != ']')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (strlen(name) == (p - s) && strncmp(s, name, p - s) == 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
// Skip ']' or '>'
|
||||
while (*p != '\0' && (*p == ']' || *p == '>')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t cli_arg_count(cli_t* cli) { return cli->args_count; }
|
||||
|
||||
bool cli_has_nth_arg(cli_t* cli, int n) {
|
||||
return n >= 0 && n < cli->args_count;
|
||||
}
|
||||
|
||||
bool cli_has_arg(cli_t* cli, const char* name) {
|
||||
return cli_has_nth_arg(cli, find_arg(cli->current_cmd, name));
|
||||
}
|
||||
|
||||
const char* cli_nth_arg(cli_t* cli, int n) {
|
||||
if (n >= 0 && n < cli->args_count) {
|
||||
return cli->args[n];
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const char* cli_arg(cli_t* cli, const char* name) {
|
||||
return cli_nth_arg(cli, find_arg(cli->current_cmd, name));
|
||||
}
|
||||
|
||||
bool cli_nth_arg_uint32(cli_t* cli, int n, uint32_t* result) {
|
||||
const char* arg = cli_nth_arg(cli, n);
|
||||
return cstr_parse_uint32(arg, 0, result);
|
||||
}
|
||||
|
||||
bool cli_arg_uint32(cli_t* cli, const char* name, uint32_t* result) {
|
||||
const char* arg = cli_arg(cli, name);
|
||||
return cstr_parse_uint32(arg, 0, result);
|
||||
}
|
||||
|
||||
bool cli_arg_hex(cli_t* cli, const char* name, uint8_t* dst, size_t dst_len,
|
||||
size_t* bytes_written) {
|
||||
const char* arg = cli_arg(cli, name);
|
||||
return cstr_decode_hex(arg, dst, dst_len, bytes_written);
|
||||
}
|
198
core/embed/rtl/inc/rtl/cli.h
Normal file
198
core/embed/rtl/inc/rtl/cli.h
Normal file
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <trezor_types.h>
|
||||
|
||||
#include <rtl/strutils.h>
|
||||
|
||||
typedef struct cli cli_t;
|
||||
|
||||
// Maximum length of command line input (including command, arguments)
|
||||
#define CLI_LINE_BUFFER_SIZE 4096
|
||||
// Maximum number of command arguments + 1
|
||||
#define CLI_MAX_ARGS 64
|
||||
|
||||
// Maximum length of command line in history buffer
|
||||
// (lines longer than this limit are not recorder)
|
||||
#define CLI_HISTORY_LINE_SIZE 256
|
||||
// Depth of the history buffer
|
||||
#define CLI_HISTORY_DEPTH 5
|
||||
|
||||
// Error codes
|
||||
#define CLI_ERROR "error" // unspecified error
|
||||
#define CLI_ERROR_INVALID_CMD "invalid-cmd"
|
||||
#define CLI_ERROR_INVALID_ARG "invalid-arg"
|
||||
#define CLI_ERROR_ABORT "abort"
|
||||
#define CLI_ERROR_FATAL "fatal"
|
||||
#define CLI_ERROR_TIMEOUT "timeout"
|
||||
#define CLI_ERROR_LOCKED "locked"
|
||||
#define CLI_ERROR_NODATA "no-data"
|
||||
|
||||
// CLI command handler routine prototype
|
||||
typedef void (*cli_cmd_handler_t)(cli_t* cli);
|
||||
|
||||
// Structure describing the registration record for a CLI command handler
|
||||
typedef struct {
|
||||
// Command name
|
||||
const char* name;
|
||||
// Command handler
|
||||
cli_cmd_handler_t func;
|
||||
// Single line command description
|
||||
const char* info;
|
||||
// Arguments definition
|
||||
// "<mandatory-arg> [<optional-arg>] [--flag1 | --flag2]"
|
||||
// NOTE: optional args must be placed after mandatory args
|
||||
const char* args;
|
||||
} cli_command_t;
|
||||
|
||||
#define CONCAT_INDIRECT(x, y) x##y
|
||||
#define CONCAT(x, y) CONCAT_INDIRECT(x, y)
|
||||
|
||||
// Registers a command handler by placing its registration structure
|
||||
// into a specially designated linker script section
|
||||
#define PRODTEST_CLI_CMD(...) \
|
||||
__attribute__((used, \
|
||||
section(".prodtest_cli_cmd"))) static const cli_command_t \
|
||||
CONCAT(_cli_cmd_handler, __COUNTER__) = {__VA_ARGS__};
|
||||
|
||||
// Callback for writing characters to console output
|
||||
typedef size_t (*cli_write_cb_t)(void* ctx, const char* buf, size_t len);
|
||||
// Callback for reading characters from console input
|
||||
typedef size_t (*cli_read_cb_t)(void* ctx, char* buf, size_t len);
|
||||
|
||||
struct cli {
|
||||
// I/O callbacks
|
||||
cli_read_cb_t read;
|
||||
cli_write_cb_t write;
|
||||
void* callback_context;
|
||||
|
||||
// Registered command handlers
|
||||
const cli_command_t* cmd_array;
|
||||
size_t cmd_count;
|
||||
|
||||
// Current line buffer
|
||||
char line_buffer[CLI_LINE_BUFFER_SIZE];
|
||||
// Command name (pointer to the line buffer)
|
||||
const char* cmd_name;
|
||||
// Number of parsed arguments
|
||||
size_t args_count;
|
||||
// Parsed arguments (pointers to the line buffer)
|
||||
const char* args[CLI_MAX_ARGS];
|
||||
// Currently processed command
|
||||
const cli_command_t* current_cmd;
|
||||
|
||||
// Command history
|
||||
char history[CLI_HISTORY_DEPTH][CLI_HISTORY_LINE_SIZE];
|
||||
// History head index (the most recent command)
|
||||
int history_head;
|
||||
|
||||
// Final status (OK/ERROR) was sent by the command handler
|
||||
bool final_status;
|
||||
// Interactive mode
|
||||
bool interactive;
|
||||
// Empty line counter
|
||||
int empty_lines;
|
||||
// Flag set by `cli_abort()` to indicate the command should
|
||||
// finish as soon as possible with an CLI_ERROR_ABORT
|
||||
volatile bool aborted;
|
||||
};
|
||||
|
||||
// Initializes the command line structure
|
||||
bool cli_init(cli_t* cli, cli_read_cb_t read, cli_write_cb_t write,
|
||||
void* callback_context);
|
||||
|
||||
// Registers the command handlers
|
||||
void cli_set_commands(cli_t* cli, const cli_command_t* cmd_array,
|
||||
size_t cmd_count);
|
||||
|
||||
// Runs the CLI command loop
|
||||
void cli_run_loop(cli_t* cli);
|
||||
|
||||
// Returne the number of arguments in the command line
|
||||
size_t cli_arg_count(cli_t* cli);
|
||||
|
||||
// Returns the n-th argument from the command line.
|
||||
//
|
||||
// Indexing starts at 0, meaning the first argument is at index 0.
|
||||
// Returns an empty string if the argument is not present.
|
||||
const char* cli_nth_arg(cli_t* cli, int n);
|
||||
|
||||
// Returns the argument with the given name from the command line.
|
||||
//
|
||||
// Returns an empty string if the argument is not present.
|
||||
const char* cli_arg(cli_t* cli, const char* name);
|
||||
|
||||
// Returns true if the n-th argument is present.
|
||||
bool cli_has_nth_arg(cli_t* cli, int n);
|
||||
|
||||
// Returns true if the argument with the given name is present.
|
||||
bool cli_has_arg(cli_t* cli, const char* name);
|
||||
|
||||
// Parses the argument with the given name as an unsigned 32-bit integer.
|
||||
//
|
||||
// The result is set only if the argument is present and can be parsed.
|
||||
// Otherwise, the function returns false and the result is not modified.
|
||||
bool cli_arg_uint32(cli_t* cli, const char* name, uint32_t* result);
|
||||
|
||||
// Parses the argument with the given name as a hexadecimal string.
|
||||
//
|
||||
// (see cstr_parse_hex() for details)
|
||||
bool cli_arg_hex(cli_t* cli, const char* name, uint8_t* dst, size_t dst_len,
|
||||
size_t* bytes_written);
|
||||
|
||||
// Writes a formatted trace string to the console. The formatted string is
|
||||
// automatically prefixed with the "#" character and terminated with
|
||||
// CR/LF characters.
|
||||
void cli_trace(cli_t* cli, const char* format, ...);
|
||||
|
||||
// Writes a formatted OK response to the console. The formatted string is
|
||||
// automatically prefixed with the "OK" string and terminated with CR/LF
|
||||
// characters.
|
||||
void cli_ok(cli_t* cli, const char* format, ...);
|
||||
|
||||
// Write OK response with hex-encoded data
|
||||
void cli_ok_hexdata(cli_t* cli, const void* data, size_t size);
|
||||
|
||||
// Writes a formatted progress message to the console. The formatted string is
|
||||
// automatically prefixed with the "PROGRESS" string and terminated with CR/LF
|
||||
// characters.
|
||||
void cli_progress(cli_t* cli, const char* format, ...);
|
||||
|
||||
// Writes a formatted error message to the console. The formatted string is
|
||||
// automatically prefixed with the "ERROR" string and terminated with CR/LF
|
||||
// characters.
|
||||
void cli_error(cli_t* cli, const char* code, const char* format, ...);
|
||||
|
||||
// Writes a invalid argument error message to the console
|
||||
// and prepends the error message with formatted trace.
|
||||
void cli_error_arg(cli_t* cli, const char* format, ...);
|
||||
|
||||
// Writes an error message to the console indicating that the number of
|
||||
// arguments is incorrect.
|
||||
void cli_error_arg_count(cli_t* cli);
|
||||
|
||||
// Aborts the current CLI command processing
|
||||
//
|
||||
// Can also be called from interrupt context
|
||||
void cli_abort(cli_t* cli);
|
||||
|
||||
// Returns true if `cli_abort()` was called
|
||||
bool cli_aborted(cli_t* cli);
|
65
core/embed/rtl/inc/rtl/strutils.h
Normal file
65
core/embed/rtl/inc/rtl/strutils.h
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <trezor_types.h>
|
||||
|
||||
// Parses the string as a signed 32-bit integer in the specified base.
|
||||
//
|
||||
// If the entire string represents a valid integer, the parsed value is stored
|
||||
// in 'result', and the function returns true. Otherwise, the function returns
|
||||
// false, and 'result' remains unchanged.
|
||||
bool cstr_parse_int32(const char* str, int base, int32_t* result);
|
||||
|
||||
// Parses the string as a unsigned 32-bit integer in the specified base.
|
||||
//
|
||||
// If the entire string represents a valid integer, the parsed value is stored
|
||||
// in 'result', and the function returns true. Otherwise, the function returns
|
||||
// false, and 'result' remains unchanged.
|
||||
bool cstr_parse_uint32(const char* str, int base, uint32_t* result);
|
||||
|
||||
// Skips leading whitespace in the string and returns the pointer to the first
|
||||
// non-whitespace character.
|
||||
const char* cstr_skip_whitespace(const char* str);
|
||||
|
||||
// Returns true if the null-terminated C-string starts with the prefix
|
||||
bool cstr_starts_with(const char* str, const char* prefix);
|
||||
|
||||
// Decodes the string as a hexadecimal string and writes the binary data to the
|
||||
// destination buffer.
|
||||
//
|
||||
// Hexadecimal digits can be in upper or lower case and may be separated by
|
||||
// whitespace.
|
||||
//
|
||||
// Number of bytes written to the destination buffer is stored in
|
||||
// `bytes_written` (even if the function returns false).
|
||||
//
|
||||
// Returns true if the entire slice was parsed successfully.
|
||||
bool cstr_decode_hex(const char* str, uint8_t* dst, size_t dst_len,
|
||||
size_t* bytes_written);
|
||||
|
||||
// Encodes binary data to null-terminated hexadecimal string
|
||||
//
|
||||
// Destination buffer must be at least 2 * src_len + 1 bytes long.
|
||||
//
|
||||
// If the destination buffer is too small, the function will return false and
|
||||
// the destination buffer will be set to empty string.
|
||||
bool cstr_encode_hex(char* dst, size_t dst_len, const void* src,
|
||||
size_t src_len);
|
114
core/embed/rtl/strutils.c
Normal file
114
core/embed/rtl/strutils.c
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#include <rtl/strutils.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
bool cstr_parse_int32(const char* str, int base, int32_t* result) {
|
||||
char* endptr;
|
||||
int32_t value = strtol(str, &endptr, base);
|
||||
if (endptr > str && endptr == str + strlen(str)) {
|
||||
*result = value;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool cstr_parse_uint32(const char* str, int base, uint32_t* result) {
|
||||
char* endptr;
|
||||
uint32_t value = strtoul(str, &endptr, base);
|
||||
if (endptr > str && endptr == str + strlen(str)) {
|
||||
*result = value;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const char* cstr_skip_whitespace(const char* str) {
|
||||
while (isspace((unsigned char)*str)) {
|
||||
str++;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
bool cstr_starts_with(const char* str, const char* prefix) {
|
||||
size_t prefix_len = strlen(prefix);
|
||||
return strlen(str) >= prefix_len && 0 == strncmp(str, prefix, prefix_len);
|
||||
}
|
||||
|
||||
static inline bool parse_nibble(char c, uint32_t* value) {
|
||||
uint8_t nibble = 0;
|
||||
|
||||
if (c >= '0' && c <= '9') {
|
||||
nibble = c - '0';
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
nibble = c - 'A' + 10;
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
nibble = c - 'a' + 10;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
*value = (*value << 4) + nibble;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cstr_decode_hex(const char* str, uint8_t* dst, size_t dst_len,
|
||||
size_t* bytes_written) {
|
||||
size_t idx = 0;
|
||||
|
||||
while (idx < dst_len) {
|
||||
str = cstr_skip_whitespace(str);
|
||||
uint32_t value = 0;
|
||||
if (!parse_nibble(str[0], &value)) break;
|
||||
if (!parse_nibble(str[1], &value)) break;
|
||||
dst[idx++] = value;
|
||||
str += 2;
|
||||
}
|
||||
|
||||
*bytes_written = idx;
|
||||
|
||||
return *cstr_skip_whitespace(str) == '\0';
|
||||
}
|
||||
|
||||
bool cstr_encode_hex(char* dst, size_t dst_len, const void* src,
|
||||
size_t src_len) {
|
||||
static const char hex[] = "0123456789ABCDEF";
|
||||
|
||||
if (dst_len < src_len * 2 + 1) {
|
||||
if (dst_len > 0) {
|
||||
dst[0] = '\0';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < src_len; i++) {
|
||||
dst[i * 2] = hex[((uint8_t*)src)[i] >> 4];
|
||||
dst[i * 2 + 1] = hex[((uint8_t*)src)[i] & 0x0F];
|
||||
}
|
||||
dst[src_len * 2] = '\0';
|
||||
|
||||
return true;
|
||||
}
|
@ -52,6 +52,12 @@ SECTIONS {
|
||||
*(.text*);
|
||||
. = ALIGN(4);
|
||||
*(.rodata*);
|
||||
. = ALIGN(4);
|
||||
|
||||
_prodtest_cli_cmd_section_start = .;
|
||||
KEEP(*(.prodtest_cli_cmd))
|
||||
_prodtest_cli_cmd_section_end = .;
|
||||
|
||||
. = ALIGN(128K);
|
||||
} >FLASH
|
||||
|
||||
|
@ -54,6 +54,12 @@ SECTIONS {
|
||||
*(.text*);
|
||||
. = ALIGN(4);
|
||||
*(.rodata*);
|
||||
. = ALIGN(4);
|
||||
|
||||
_prodtest_cli_cmd_section_start = .;
|
||||
KEEP(*(.prodtest_cli_cmd))
|
||||
_prodtest_cli_cmd_section_end = .;
|
||||
|
||||
. = ALIGN(512);
|
||||
} >FLASH AT>FLASH
|
||||
|
||||
|
@ -60,6 +60,12 @@ SECTIONS {
|
||||
*(.text*);
|
||||
. = ALIGN(4);
|
||||
*(.rodata*);
|
||||
. = ALIGN(4);
|
||||
|
||||
_prodtest_cli_cmd_section_start = .;
|
||||
KEEP(*(.prodtest_cli_cmd))
|
||||
_prodtest_cli_cmd_section_end = .;
|
||||
|
||||
. = ALIGN(512);
|
||||
} >FLASH AT>FLASH
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
// - range of np1300 is 32-800mA
|
||||
// - used battery limit is 180mA
|
||||
#define NPM1300_CHARGING_LIMIT_MIN 32 // mA
|
||||
#define NPM1300_CHARGING_LIMIT_MAX 800 // mA // !@# TODO: set to 180mA
|
||||
#define NPM1300_CHARGING_LIMIT_MAX 180 // mA
|
||||
#define NPM1300_CHARGING_LIMIT_DEFAULT 180 // mA
|
||||
|
||||
typedef struct {
|
||||
|
@ -9,6 +9,7 @@ from typing_extensions import Self
|
||||
import click
|
||||
import requests
|
||||
import serial
|
||||
import shlex
|
||||
|
||||
SERVER_TOKEN = ""
|
||||
SERVER_URL = "http://localhost:8000/provision"
|
||||
@ -31,20 +32,20 @@ class ProvisioningResult:
|
||||
)
|
||||
|
||||
def write(self, connection: Connection) -> None:
|
||||
connection.command("CERTDEV WRITE", self.device_cert)
|
||||
cert_dev = connection.command("CERTDEV READ")
|
||||
connection.command("optiga-certdev-write", self.device_cert)
|
||||
cert_dev = connection.command("optiga-certdev-read")
|
||||
if cert_dev != self.device_cert:
|
||||
print("Device certificate mismatch")
|
||||
print("Expected:", self.device_cert)
|
||||
print("Got: ", cert_dev)
|
||||
assert cert_dev == self.device_cert
|
||||
|
||||
connection.command("CERTFIDO WRITE", self.fido_cert)
|
||||
cert_fido = connection.command("CERTFIDO READ")
|
||||
connection.command("optiga-certfido-write", self.fido_cert)
|
||||
cert_fido = connection.command("optiga-certfido-read")
|
||||
assert cert_fido == self.fido_cert
|
||||
|
||||
connection.command("KEYFIDO WRITE", self.fido_privkey)
|
||||
key_fido = connection.command("KEYFIDO READ")
|
||||
connection.command("optiga-keyfido-write", self.fido_privkey)
|
||||
key_fido = connection.command("optiga-keyfido-read")
|
||||
assert key_fido is not None
|
||||
assert key_fido in self.fido_cert
|
||||
|
||||
@ -57,9 +58,9 @@ class DeviceInfo:
|
||||
|
||||
@classmethod
|
||||
def read(cls, connection: Connection) -> Self:
|
||||
cpu_id = connection.command("CPUID READ")
|
||||
optiga_id = connection.command("OPTIGAID READ")
|
||||
cert_bytes = connection.command("CERTINF READ")
|
||||
cpu_id = connection.command("get-cpuid")
|
||||
optiga_id = connection.command("optiga-id-read")
|
||||
cert_bytes = connection.command("optiga-certinf-read")
|
||||
assert optiga_id is not None
|
||||
assert cpu_id is not None
|
||||
assert cert_bytes is not None
|
||||
@ -97,15 +98,7 @@ class Connection:
|
||||
print("!!!", byte, "is not printable")
|
||||
continue
|
||||
self.connection.write(bytes([byte]))
|
||||
echo = self.connection.read(1)
|
||||
assert echo[0] == byte
|
||||
self.connection.write(b"\r")
|
||||
assert self.connection.read(2) == b"\r\n"
|
||||
# self.connection.write(data + b"\r")
|
||||
# echo = self.connection.read(len(data) + 2)
|
||||
# print(len(echo), len(data) + 2)
|
||||
# assert echo[:-2] == data
|
||||
# assert echo[-2:] == b"\r\n"
|
||||
|
||||
def command(self, cmd: str, *args: Any) -> bytes | None:
|
||||
cmd_line = cmd
|
||||
@ -116,12 +109,16 @@ class Connection:
|
||||
cmd_line += " " + str(arg)
|
||||
self.writeline(cmd_line.encode())
|
||||
|
||||
while True:
|
||||
res = self.readline()
|
||||
if res.startswith(b"ERROR"):
|
||||
error_text = res[len(b"ERROR ") :].decode()
|
||||
error_args = res[len(b"ERROR ") :].decode()
|
||||
parts = shlex.split(error_args)
|
||||
error_text = parts[0] # error code
|
||||
if len(parts) > 1:
|
||||
error_text = parts[1] # error description
|
||||
raise ProdtestException(error_text)
|
||||
if not res.startswith(b"OK"):
|
||||
raise ProdtestException("Unexpected response: " + res.decode())
|
||||
elif res.startswith(b"OK"):
|
||||
res_arg = res[len(b"OK ") :]
|
||||
if not res_arg:
|
||||
return None
|
||||
@ -129,10 +126,11 @@ class Connection:
|
||||
return bytes.fromhex(res_arg.decode())
|
||||
except ValueError:
|
||||
return res_arg
|
||||
|
||||
elif not res.startswith(b"#"):
|
||||
raise ProdtestException("Unexpected response: " + res.decode())
|
||||
|
||||
def provision_request(
|
||||
device: DeviceInfo, url: str, verify: bool = True
|
||||
device: DeviceInfo, url: str, model: str, verify: bool = True
|
||||
) -> ProvisioningResult:
|
||||
request = {
|
||||
"tester_id": SERVER_TOKEN,
|
||||
@ -140,9 +138,9 @@ def provision_request(
|
||||
"optiga_id": device.optiga_id.hex(),
|
||||
"cpu_id": device.cpu_id.hex(),
|
||||
"cert": device.device_cert.hex(),
|
||||
"model": "T2B1",
|
||||
"model": model,
|
||||
}
|
||||
resp = requests.post(url, json=request, verify=verify)
|
||||
resp = requests.post(url + '/provision', json=request, verify=verify)
|
||||
if resp.status_code == 400:
|
||||
print("Server returned error:", resp.text)
|
||||
resp.raise_for_status()
|
||||
@ -156,32 +154,35 @@ def cli() -> None:
|
||||
|
||||
|
||||
@cli.command()
|
||||
def identify() -> None:
|
||||
connection = Connection()
|
||||
connection.command("PING")
|
||||
@click.option("-d", "--device", default="/dev/ttyACM0", help="Device path")
|
||||
def identify(device) -> None:
|
||||
connection = Connection(device)
|
||||
connection.command("ping")
|
||||
DeviceInfo.read(connection)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("-d", "--device", default="/dev/ttyACM0", help="Device path")
|
||||
@click.option("--wipe", is_flag=True, help="Wipe the device")
|
||||
def lock(wipe) -> None:
|
||||
connection = Connection()
|
||||
connection.command("PING")
|
||||
connection.command("LOCK")
|
||||
def lock(device, wipe) -> None:
|
||||
connection = Connection(device)
|
||||
connection.command("ping")
|
||||
connection.command("optiga-lock")
|
||||
if wipe:
|
||||
connection.command("WIPE")
|
||||
connection.command("prodtest-wipe")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("-u", "--url", default=SERVER_URL, help="Server URL")
|
||||
@click.option("-d", "--device", default="/dev/ttyACM0", help="Device path")
|
||||
@click.option("-m", "--model", help="Device path")
|
||||
@click.option(
|
||||
"--no-verify", is_flag=True, help="Disable server certificate verification"
|
||||
)
|
||||
@click.option(
|
||||
"--lock/--no-lock", default=True, help="Lock the device after provisioning"
|
||||
)
|
||||
def provision(url, device, no_verify, lock) -> None:
|
||||
def provision(url, device, model, no_verify, lock) -> None:
|
||||
global SERVER_TOKEN
|
||||
|
||||
SERVER_TOKEN = os.environ.get("SERVER_TOKEN")
|
||||
@ -190,18 +191,18 @@ def provision(url, device, no_verify, lock) -> None:
|
||||
connection = Connection(device)
|
||||
|
||||
# test the connection
|
||||
connection.command("PING")
|
||||
connection.command("ping")
|
||||
|
||||
# grab CPUID, OPTIGAID and device certificate
|
||||
device = DeviceInfo.read(connection)
|
||||
# call the provisioning server
|
||||
result = provision_request(device, url, not no_verify)
|
||||
result = provision_request(device, url, model, not no_verify)
|
||||
# write provisioning result to the device
|
||||
result.write(connection)
|
||||
|
||||
if lock:
|
||||
connection.command("LOCK")
|
||||
connection.command("WIPE")
|
||||
connection.command("optiga-lock")
|
||||
connection.command("prodtest-wipe")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
Loading…
Reference in New Issue
Block a user