Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 52 additions & 4 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion presubmit/cpp_include_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def guard_name(path: Path) -> str:
# The presubmit tool runs in the root of the project.
# Compute the path relative to the project root.
path = path.relative_to(os.getcwd())
guard = f"{PROJECT}_{path}".replace("/", "_").replace(".", "_")
guard = f"{PROJECT}_{path}_".replace("/", "_").replace(".", "_")
return guard.upper()


Expand Down
28 changes: 28 additions & 0 deletions third_party/crates_io/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions third_party/crates_io/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ smlang = { version = "0.8.0", default-features = false }
syn = { version = "2.0.104", features = ["full", "extra-traits"] }
syn1 = { package = "syn", version = "1.0.109", features = ["full", "extra-traits"] }
tock-registers = "0.9.0"
ufmt = "0.2.0"
zerocopy = { version = "0.8.48", default-features = false, features = ["derive"] }
zeroize = { version = "1.8", default-features = false, features = ["derive"] }

Expand Down
67 changes: 67 additions & 0 deletions util/console/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0

load("@rules_rust//rust:defs.bzl", "rust_doc", "rust_library", "rust_static_library", "rust_test")

package(default_visibility = ["//visibility:public"])

rust_library(
name = "console",
srcs = [
"hexdump.rs",
"lib.rs",
],
crate_name = "util_console",
edition = "2024",
deps = [
":dbg_print",
"@rust_crates//:ufmt",
"@rust_crates//:zerocopy",
] + select({
"@platforms//os:none": [
":pigweed",
],
"//conditions:default": [":stdout"],
}),
)

rust_doc(
name = "console_doc",
crate = ":console",
)

rust_static_library(
name = "stdout",
srcs = [
"stdout.rs",
],
crate_name = "console_stdout",
edition = "2024",
)

rust_static_library(
name = "pigweed",
srcs = select({
"@platforms//cpu:armv8-m": ["pigweed_arm.rs"],
"@platforms//cpu:riscv32": ["pigweed_riscv.rs"],
"//conditions:default": ["pigweed_other.rs"],
}),
crate_name = "console_pigweed",
edition = "2024",
rustc_flags = [
"-C",
"panic=abort",
],
)

cc_library(
name = "dbg_print",
srcs = ["dbg_print.c"],
hdrs = ["dbg_print.h"],
alwayslink = 1,
)

rust_test(
name = "console_test",
crate = ":console",
)
19 changes: 19 additions & 0 deletions util/console/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# `util_console`

This subdirectory contains the module `util_console`, which is a generic
output-only console.

The `pw_log` implementation is nice, but is fundamentally tied to
`printf`-like format strings. In contrast, most Rust code that prints
text is structured around the `Display` and `Debug` traits. As these
traits are rather heavyweight and require dynamic dispatch, there are
replacement crates (like `ufmt`) which provide a `Debug`/`Display`-like
interface, but are designed for embedded firmware.

The `util_console` crate implements a `ufmt`-based console that can be
used by firmware or host code (e.g. tests) that sends output to the same
output device as the `pw_log` macros.

In addition to supplying rust `print` and `trace` macros, `util_console`
includes a very minimal C `printf` implementation. The `printf`
implementation is meant to be used in C-based firmware code.
135 changes: 135 additions & 0 deletions util/console/dbg_print.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

#include "util/console/dbg_print.h"

#include <assert.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>

extern void system_lowlevel_console_write(const char *buf, size_t len);

static const char kHexTable[17] = "0123456789abcdef";

static size_t print_integer(char *dest, unsigned value, bool is_signed) {
char buf[12];
char *b = buf + sizeof(buf);
size_t len = 0;
if (is_signed && (int)value < 0) {
*dest++ = ('-');
len++;
value = (unsigned)(-(int)value);
}
*--b = '\0';
do {
*--b = '0' + value % 10;
value /= 10;
} while (value);
while (*b) {
*dest++ = (*b++);
len++;
}
return len;
}

void dbg_printf(const char *format, ...) {
char buffer[256];
char *buf = buffer;

va_list args;
va_start(args, format);

for (; *format != '\0'; ++format) {
if (*format != '%') {
*buf++ = *format;
continue;
}

++format; // Skip over the '%'.
switch (*format) {
case '%':
*buf++ = *format;
break;
case 'c': {
int ch = va_arg(args, int);
*buf++ = (char)ch;
break;
}
case 'C': {
uint32_t val = va_arg(args, uint32_t);
for (size_t i = 0; i < sizeof(uint32_t); ++i, val >>= 8) {
uint8_t ch = (uint8_t)val;
if (ch >= 32 && ch < 127) {
*buf++ = (char)ch;
} else {
*buf++ = ('\\');
*buf++ = ('x');
*buf++ = (kHexTable[ch >> 4]);
*buf++ = (kHexTable[ch & 15]);
}
}
break;
}
case 's': {
// Print a null-terminated string.
const char *str = va_arg(args, const char *);
while (*str != '\0') {
if (buf >= buffer + sizeof(buffer) - 1) {
// Bail out if the string would overrun the buffer.
break;
}
*buf++ = (*str++);
Comment thread
cfrantz marked this conversation as resolved.
}
break;
}
case 'd':
// `print_integer` will handle the sign bit of the value.
buf += print_integer(buf, va_arg(args, unsigned), true);
break;
case 'u':
buf += print_integer(buf, va_arg(args, unsigned), false);
break;
case 'p':
case 'x': {
// Print an `unsigned int` in hexadecimal.
unsigned int v = va_arg(args, unsigned int);
for (size_t i = 0; i < sizeof(v) * 2; ++i) {
int shift = sizeof(v) * 8 - 4;
*buf++ = (kHexTable[v >> shift]);
v <<= 4;
}
break;
}
default:
// For an invalid format specifier, back up one char and allow
// the output via the normal mechanism.
*buf++ = ('%');
--format;
}
}
va_end(args);
system_lowlevel_console_write(buffer, buf - buffer);
}

void dbg_hexdump(const void *data, size_t len) {
const uint8_t *p = (const uint8_t *)data;
size_t j = 0;

while (j < len) {
// hexbuf is initialized as 48 spaces followed by a nul byte.
char hexbuf[] = " ";
// ascii is initialized as 17 nul bytes.
char ascii[17] = {
0,
};
dbg_printf("%p: ", p);
for (size_t i = 0; i < 16 && j < len; ++p, ++i, ++j) {
uint8_t val = *p;
hexbuf[i * 3 + 0] = kHexTable[val >> 4];
hexbuf[i * 3 + 1] = kHexTable[val & 15];
ascii[i] = (val >= 32 && val < 127) ? (char)val : '.';
}
dbg_printf("%s %s\r\n", hexbuf, ascii);
}
}
53 changes: 53 additions & 0 deletions util/console/dbg_print.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

#ifndef OPENPROT_UTIL_CONSOLE_DBG_PRINT_H_
#define OPENPROT_UTIL_CONSOLE_DBG_PRINT_H_

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

/**
* An intentionally pared-down implementation of `printf()` that writes
* to UART0.
*
* This function only supports the format specifiers required by the
* ROM:
* - %c prints a single character.
* - %C prints a 'FourCC' style uint32_t (ASCII bytes in little-endian order).
* - %d prints a signed int in decimal.
* - %u prints an unsigned int in decimal.
* - %s prints a nul-terminated string.
* - %p prints pointer in hexadecimal.
* - %x prints an `unsigned int` in hexadecimal using lowercase characters.
*
* No modifiers are supported and the leading zeros in hexidecimal
* values are always printed.
*
* Note: unfortunately `uint32_t` is not necessarily an alias for
* `unsigned int`. An explicit cast is therefore necessary when printing
* `uint32_t` values using the `%x` format specifier in order to satisfy
* the `printf` format checker (`-Wformat`).
*
* @param format The format specifier.
* @param ... The values to interpolate in the format.
* @return The result of the operation.
*/
void dbg_printf(const char *format, ...) __attribute__((format(printf, 1, 2)));

/**
* Hexdump a region of memory.
*
* @param data The memory to dump.
* @param len The length of the region to dump.
*/
void dbg_hexdump(const void *data, size_t len);

#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus

#endif // OPENPROT_UTIL_CONSOLE_DBG_PRINT_H_
Loading