Skip to content

Commit a0440ba

Browse files
bors[bot]kupiakos
andauthored
Merge #370
370: Add a simple way to convert a CommandReturn into a Result r=jrvanwhy a=kupiakos There are a few changes that this makes: - `CommandReturn` is `#[must_use]` - `ErrorCode` includes `BadRVal` - `CommandReturn` has a `to_result` method I experimented with a few methods of structuring `to_result` to have the smallest overhead per monomorphization. What you see here is the best I could do. I also tried: - Providing a `try_to_result` and using that (lots of overhead) - Preserving the error code of the failure variant on `BADRVAL` - Associated constant `FailureData::BADRVAL` - Associated fn `FailureData::from_wrong_variant(r1: ErrorCode) -> Self` - A helper function `fn to_result_helper(self, success_variant: ReturnVariant, error_variant: ReturnVariant) -> (bool, u32, u32, u32)` that would do the branches and replacing `r1` with `BadRVal` and other registers with `0` in the case of the wrong variant. I thought this would reduce things further but it seemed to make it worse in my tests. Then again - I only had one instance of each monomorphization. Co-authored-by: Alyssa Haroldsen <kupiakos@google.com>
2 parents abff51c + cb128cb commit a0440ba

File tree

8 files changed

+440
-43
lines changed

8 files changed

+440
-43
lines changed

apis/leds/src/lib.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![no_std]
22

3-
use libtock_platform::Syscalls;
3+
use libtock_platform::{ErrorCode, Syscalls};
44

55
/// The LEDs driver
66
///
@@ -9,30 +9,30 @@ use libtock_platform::Syscalls;
99
/// use libtock2::Leds;
1010
///
1111
/// // Turn on led 0
12-
/// Leds::on(0);
12+
/// let _ = Leds::on(0);
1313
/// ```
1414
pub struct Leds<S: Syscalls>(S);
1515

1616
impl<S: Syscalls> Leds<S> {
1717
/// Run a check against the leds capsule to ensure it is present.
1818
///
19-
/// Returns `Some(number_of_leds)` if the driver was present. This does not necessarily mean
19+
/// Returns `Ok(number_of_leds)` if the driver was present. This does not necessarily mean
2020
/// that the driver is working, as it may still fail to allocate grant
2121
/// memory.
22-
pub fn count() -> Option<u32> {
23-
S::command(DRIVER_ID, LEDS_COUNT, 0, 0).get_success_u32()
22+
pub fn count() -> Result<u32, ErrorCode> {
23+
S::command(DRIVER_ID, LEDS_COUNT, 0, 0).to_result()
2424
}
2525

26-
pub fn on(led: u32) {
27-
S::command(DRIVER_ID, LED_ON, led, 0);
26+
pub fn on(led: u32) -> Result<(), ErrorCode> {
27+
S::command(DRIVER_ID, LED_ON, led, 0).to_result()
2828
}
2929

30-
pub fn off(led: u32) {
31-
S::command(DRIVER_ID, LED_OFF, led, 0);
30+
pub fn off(led: u32) -> Result<(), ErrorCode> {
31+
S::command(DRIVER_ID, LED_OFF, led, 0).to_result()
3232
}
3333

34-
pub fn toggle(led: u32) {
35-
S::command(DRIVER_ID, LED_TOGGLE, led, 0);
34+
pub fn toggle(led: u32) -> Result<(), ErrorCode> {
35+
S::command(DRIVER_ID, LED_TOGGLE, led, 0).to_result()
3636
}
3737
}
3838

apis/leds/src/tests.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
use libtock_platform::ErrorCode;
12
use libtock_unittest::fake;
23

34
type Leds = super::Leds<fake::Syscalls>;
45

56
#[test]
67
fn no_driver() {
78
let _kernel = fake::Kernel::new();
8-
assert_eq!(Leds::count(), None);
9+
assert_eq!(Leds::count(), Err(ErrorCode::NoDevice));
910
}
1011

1112
#[test]
@@ -14,7 +15,7 @@ fn driver_check() {
1415
let driver = fake::Leds::<10>::new();
1516
kernel.add_driver(&driver);
1617

17-
assert!(Leds::count().is_some());
18+
assert_eq!(Leds::count(), Ok(10));
1819
for led in 0..10 {
1920
assert_eq!(driver.get_led(led), Some(false));
2021
}
@@ -34,7 +35,7 @@ fn on() {
3435
let driver = fake::Leds::<10>::new();
3536
kernel.add_driver(&driver);
3637

37-
Leds::on(0);
38+
assert_eq!(Leds::on(0), Ok(()));
3839
assert_eq!(driver.get_led(0), Some(true));
3940
}
4041

@@ -44,7 +45,7 @@ fn off() {
4445
let driver = fake::Leds::<10>::new();
4546
kernel.add_driver(&driver);
4647

47-
Leds::off(0);
48+
assert_eq!(Leds::off(0), Ok(()));
4849
assert_eq!(driver.get_led(0), Some(false));
4950
}
5051

@@ -54,9 +55,9 @@ fn toggle() {
5455
let driver = fake::Leds::<10>::new();
5556
kernel.add_driver(&driver);
5657

57-
Leds::toggle(0);
58+
assert_eq!(Leds::toggle(0), Ok(()));
5859
assert_eq!(driver.get_led(0), Some(true));
59-
Leds::toggle(0);
60+
assert_eq!(Leds::toggle(0), Ok(()));
6061
assert_eq!(driver.get_led(0), Some(false));
6162
}
6263

@@ -66,9 +67,9 @@ fn on_off() {
6667
let driver = fake::Leds::<10>::new();
6768
kernel.add_driver(&driver);
6869

69-
Leds::on(0);
70+
assert_eq!(Leds::on(0), Ok(()));
7071
assert_eq!(driver.get_led(0), Some(true));
71-
Leds::off(0);
72+
assert_eq!(Leds::off(0), Ok(()));
7273
assert_eq!(driver.get_led(0), Some(false));
7374
}
7475

@@ -78,7 +79,7 @@ fn no_led() {
7879
let driver = fake::Leds::<10>::new();
7980
kernel.add_driver(&driver);
8081

81-
Leds::on(11);
82+
assert_eq!(Leds::on(11), Err(ErrorCode::Invalid));
8283
for led in 0..Leds::count().unwrap_or_default() {
8384
assert_eq!(driver.get_led(led), Some(false));
8485
}

libtock2/examples/leds.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ set_main! {main}
1010
stack_size! {0x100}
1111

1212
fn main() {
13-
if let Some(leds_count) = Leds::count() {
13+
if let Ok(leds_count) = Leds::count() {
1414
for led_index in 0..leds_count {
15-
Leds::on(led_index as u32);
15+
let _ = Leds::on(led_index as u32);
1616
}
1717
}
1818
}

platform/src/command_return.rs

Lines changed: 205 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,63 @@ use crate::{return_variant, ErrorCode, ReturnVariant};
22

33
use core::mem::transmute;
44

5-
/// The response type from `command`. Can represent a successful value or a
6-
/// failure.
5+
/// The response type from the [`command`](crate::Syscalls::command) syscall.
6+
/// Can represent a success or a failure with or without associated data.
7+
///
8+
/// After a syscall is made, registers `r1`-`r3` contain the output as
9+
/// described by [TRD 104][trd-104]. Some syscalls only return success/failure,
10+
/// while others provide associated data. This is done by placing the _return
11+
/// variant_ in `r0`, which specifies how the output registers should be
12+
/// interpreted. For syscalls other than `command`, the possible output
13+
/// variants are fixed; you always know which variants are expected given the
14+
/// syscall class.
15+
///
16+
/// However, the `command` syscall is flexible - there must be one success
17+
/// variant and one failure variant for a given driver/command ID, but
18+
/// which variants those are, and what data is expected, cannot be checked
19+
/// statically. Capsules and userspace APIs must agree on the expected
20+
/// variants for success and failure.
21+
///
22+
/// # Example
23+
///
24+
/// This uses the [`to_result`] method to implicitly check variants and convert
25+
/// to a `Result`.
26+
///
27+
/// ```ignore
28+
/// let res: Result<(u32, u32), ErrorCode> = Syscalls::command(314, 1, 1, 2).to_result();
29+
/// match res {
30+
/// Ok((val1, val2)) => {
31+
/// // Success with associated data in val1, val2.
32+
/// }
33+
/// Err(ErrorCode::BadRVal) => {
34+
/// // Incorrect return variant! We may choose to handle this
35+
/// // explicitly or propagate upwards without branching.
36+
/// }
37+
/// Err(ec) => {
38+
/// // The driver returned an error (or it doesn't exist).
39+
/// }
40+
/// }
41+
/// ```
42+
///
43+
/// This uses the `get_*` methods to check the variants explicitly and extract
44+
/// the associated data.
45+
///
46+
/// ```ignore
47+
/// let command_return = Syscalls::command(314, 1, 1, 2);
48+
/// if let Some((val1, val2)) = command_return.get_success_2_u32() {
49+
/// // If there was a success, there is an associated data (u32, u32).
50+
/// } else if let Some(error_code) = command_return.get_failure() {
51+
/// // If there was a failure, there's no associated data and we only
52+
/// // have an error code.
53+
/// } else {
54+
/// // Incorrect return variant! If this occurs, your capsule and userspace
55+
/// // API do not agree on what the return variants should be.
56+
/// // An application may want to panic in this case to catch this early.
57+
/// }
58+
/// ```
59+
///
60+
/// [trd-104]: https://github.com/tock/tock/blob/master/doc/reference/trd104-syscalls.md#32-return-values
61+
#[must_use = "this `CommandReturn` may represent an error, which should be handled"]
762
#[derive(Clone, Copy, Debug)]
863
pub struct CommandReturn {
964
return_variant: ReturnVariant,
@@ -26,16 +81,6 @@ impl CommandReturn {
2681
r3,
2782
}
2883
}
29-
// I generally expect CommandReturn to be used with pattern matching, e.g.:
30-
//
31-
// let command_return = Syscalls::command(314, 1, 1, 2);
32-
// if let Some((val1, val2)) = command_return.get_success_2_u32() {
33-
// // ...
34-
// } else if let Some(error_code) = command_return.get_failure() {
35-
// // ...
36-
// } else {
37-
// // Incorrect return variant
38-
// }
3984

4085
/// Returns true if this CommandReturn is of type Failure. Note that this
4186
/// does not return true for other failure types, such as Failure with u32.
@@ -177,4 +222,152 @@ impl CommandReturn {
177222
pub fn return_variant(&self) -> ReturnVariant {
178223
self.return_variant
179224
}
225+
226+
/// Interprets this `CommandReturn` as a `Result`, checking the success and
227+
/// failure variants, as well as extracting the relevant data.
228+
///
229+
/// If neither the success or failure variants match what is required by
230+
/// `T` and `E`, this function will return `Err(ErrorCode::BadRVal)`.
231+
/// If `E` contains non-`ErrorCode` data in this case, the data will be 0.
232+
///
233+
/// It is recommended to use type ascription or `::<>` to make the types
234+
/// for `T` and `E` explicit at call-site.
235+
pub fn to_result<T, E>(self) -> Result<T, E>
236+
where
237+
T: SuccessData,
238+
E: FailureData,
239+
{
240+
let (return_variant, r1, mut r2, mut r3) = self.raw_values();
241+
if return_variant == T::RETURN_VARIANT {
242+
return Ok(T::from_raw_values(r1, r2, r3));
243+
}
244+
let ec: ErrorCode = if return_variant == E::RETURN_VARIANT {
245+
// Safety: E::RETURN_VARIANT must be a failure variant, and
246+
// failure variants must contain a valid ErrorCode in r1.
247+
unsafe { core::mem::transmute(r1 as u16) }
248+
} else {
249+
r2 = 0;
250+
r3 = 0;
251+
ErrorCode::BadRVal
252+
};
253+
Err(E::from_raw_values(ec, r2, r3))
254+
}
255+
}
256+
257+
mod sealed {
258+
pub trait Sealed {}
259+
}
260+
261+
/// Output from a successful `command` syscall.
262+
///
263+
/// This trait is [sealed], meaning foreign implementations cannot be defined,
264+
/// even though it can be referenced by foreign crates.
265+
///
266+
/// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed
267+
pub trait SuccessData: sealed::Sealed {
268+
/// The return variant for this success type, stored in `r0` after
269+
/// performing a `command` syscall.
270+
const RETURN_VARIANT: ReturnVariant;
271+
272+
/// Constructs the success data given the raw register values.
273+
fn from_raw_values(r1: u32, r2: u32, r3: u32) -> Self;
274+
}
275+
276+
impl sealed::Sealed for () {}
277+
impl SuccessData for () {
278+
const RETURN_VARIANT: ReturnVariant = return_variant::SUCCESS;
279+
280+
fn from_raw_values(_r1: u32, _r2: u32, _r3: u32) -> Self {}
281+
}
282+
impl sealed::Sealed for u32 {}
283+
impl SuccessData for u32 {
284+
const RETURN_VARIANT: ReturnVariant = return_variant::SUCCESS_U32;
285+
286+
fn from_raw_values(r1: u32, _r2: u32, _r3: u32) -> Self {
287+
r1
288+
}
289+
}
290+
impl sealed::Sealed for u64 {}
291+
impl SuccessData for u64 {
292+
const RETURN_VARIANT: ReturnVariant = return_variant::SUCCESS_U64;
293+
294+
fn from_raw_values(r1: u32, r2: u32, _r3: u32) -> Self {
295+
r1 as u64 | ((r2 as u64) << 32)
296+
}
297+
}
298+
impl sealed::Sealed for (u32, u32) {}
299+
impl SuccessData for (u32, u32) {
300+
const RETURN_VARIANT: ReturnVariant = return_variant::SUCCESS_2_U32;
301+
302+
fn from_raw_values(r1: u32, r2: u32, _r3: u32) -> Self {
303+
(r1, r2)
304+
}
305+
}
306+
impl sealed::Sealed for (u32, u64) {}
307+
impl SuccessData for (u32, u64) {
308+
const RETURN_VARIANT: ReturnVariant = return_variant::SUCCESS_U32_U64;
309+
310+
fn from_raw_values(r1: u32, r2: u32, r3: u32) -> Self {
311+
(r1, r2 as u64 | ((r3 as u64) << 32))
312+
}
313+
}
314+
impl sealed::Sealed for (u32, u32, u32) {}
315+
impl SuccessData for (u32, u32, u32) {
316+
const RETURN_VARIANT: ReturnVariant = return_variant::SUCCESS_3_U32;
317+
318+
fn from_raw_values(r1: u32, r2: u32, r3: u32) -> Self {
319+
(r1, r2, r3)
320+
}
321+
}
322+
323+
/// Output from a failed `command` syscall.
324+
///
325+
/// This trait is [sealed], meaning foreign implementations cannot be defined,
326+
/// even though it can be referenced by foreign crates.
327+
///
328+
/// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed
329+
pub unsafe trait FailureData: sealed::Sealed {
330+
/// The return variant for this failure type, stored in `r0` after
331+
/// performing a `command` syscall.
332+
///
333+
/// # Safety
334+
/// This must represent a failure variant, such that `r1` will always be
335+
/// a valid [`ErrorCode`].
336+
const RETURN_VARIANT: ReturnVariant;
337+
338+
/// Constructs the error data given the raw register values.
339+
fn from_raw_values(r1: ErrorCode, r2: u32, r3: u32) -> Self;
340+
}
341+
342+
impl sealed::Sealed for ErrorCode {}
343+
unsafe impl FailureData for ErrorCode {
344+
const RETURN_VARIANT: ReturnVariant = return_variant::FAILURE;
345+
346+
fn from_raw_values(r1: ErrorCode, _r2: u32, _r3: u32) -> Self {
347+
r1
348+
}
349+
}
350+
impl sealed::Sealed for (ErrorCode, u32) {}
351+
unsafe impl FailureData for (ErrorCode, u32) {
352+
const RETURN_VARIANT: ReturnVariant = return_variant::FAILURE_U32;
353+
354+
fn from_raw_values(r1: ErrorCode, r2: u32, _r3: u32) -> Self {
355+
(r1, r2)
356+
}
357+
}
358+
impl sealed::Sealed for (ErrorCode, u32, u32) {}
359+
unsafe impl FailureData for (ErrorCode, u32, u32) {
360+
const RETURN_VARIANT: ReturnVariant = return_variant::FAILURE_2_U32;
361+
362+
fn from_raw_values(r1: ErrorCode, r2: u32, r3: u32) -> Self {
363+
(r1, r2, r3)
364+
}
365+
}
366+
impl sealed::Sealed for (ErrorCode, u64) {}
367+
unsafe impl FailureData for (ErrorCode, u64) {
368+
const RETURN_VARIANT: ReturnVariant = return_variant::FAILURE_U64;
369+
370+
fn from_raw_values(r1: ErrorCode, r2: u32, r3: u32) -> Self {
371+
(r1, r2 as u64 | ((r3 as u64) << 32))
372+
}
180373
}

0 commit comments

Comments
 (0)