From c96ba514f4cf87e7eceae776ca99018e9d0eaa68 Mon Sep 17 00:00:00 2001 From: James Munns Date: Mon, 27 Aug 2018 00:27:01 -0700 Subject: [PATCH] keep cleaning --- src/peripherals/a-first-attempt.md | 42 ++++++++++-------------------- src/peripherals/borrowck.md | 18 ++++++++++--- src/peripherals/singletons.md | 7 +++-- 3 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/peripherals/a-first-attempt.md b/src/peripherals/a-first-attempt.md index 2c6cf48..4c33785 100644 --- a/src/peripherals/a-first-attempt.md +++ b/src/peripherals/a-first-attempt.md @@ -1,6 +1,8 @@ -# Global Instances +# A First Attempt -Great! We have all of the superpowers of Rust, but how do we interact with those peripherals? They are just arbitrary memory locations, and dereferencing those would be `unsafe`! Do we need to do something like this every time we want to use a peripheral? +## Arbitrary Memory Locations and Rust + +Although Rust is capable of interacting with arbitrary memory locations, dereferencing any pointer is considered an `unsafe` operation. The most direct way to expose reading from or writing to a peripheral would look something like this: ```rust use core::ptr; @@ -18,11 +20,7 @@ fn write_serial_port_speed(val: u32) { } ``` -* Messy -* Not very Rusty -* Lets try something else... - -This is a little messy, so the first reaction might be to wrap these related things up in to a `struct` to organize them better. Maybe you would come up with something like this: +Although this works it is subjectively a little messy, so the first reaction might be to wrap these related things up in to a `struct` to better organize them. A second attempt could come up with something like this: ```rust use core::ptr; @@ -30,9 +28,12 @@ use core::ptr; struct SerialPort; impl SerialPort { + // Private Constants (addresses) const SER_PORT_SPEED_REG: *mut u32 = 0x4000_1000 as _; - pub const SER_PORT_SPEED_HI: u32 = 0x8000_0000; - pub const SER_PORT_SPEED_LO: u32 = 0x0800_0000; + + // Public Constants (enumerated values) + pub const SER_PORT_SPEED_8MBPS: u32 = 0x8000_0000; + pub const SER_PORT_SPEED_125KBPS: u32 = 0x0200_0000; fn new() -> SerialPort { SerialPort @@ -52,9 +53,6 @@ impl SerialPort { } ``` -* A little better -* This works! - And this is a little better! We've hidden that random looking memory address, and presented something that feels a little more rusty. We can even use our new interface: ```rust @@ -67,10 +65,9 @@ fn do_something() { } ``` -* Problem is you can make these anywhere -* When you can make these anywhere, you have aliased pointers! +But the problem with this is that our `SerialPort` struct could be created anywhere. By creating multiple instances if `SerialPort`, we would create aliased mutable pointers, which are typically avoided in Rust. -But the problem with this is that you can create one of these structs anywhere! Imagine this: +Consider the following example: ```rust fn do_something() { @@ -95,17 +92,6 @@ fn something_else() { } ``` -In this case, if we were only looking at the code in `do_something()`, we would think, we are definitely sending our serial data slowly, why isn't that thing working? +In this case, if we were only looking at the code in `do_something()`, we would think that we are definitely sending our serial data slowly, and would be confused why our embedded code is not working as expected. -In this example, it is easy to see. However, once this code is spread out over multiple modules, drivers, developers, and days, it gets easier and easier to make these kinds of mistakes. - -## This smells like mutable global state - -> hardware is basically nothing but mutable global state - -## What should our rules be? - -1. We should be able to share any number of read-only accesses to these peripherals -2. If something has read-write access to a peripheral, it should be the only reference - -If you are already familiar with Rust, this should start to sound familiar, as this is what the Borrow Checker already does! \ No newline at end of file +In this case, it is easy to see where the error was introduced. However, once this code is spread out over multiple modules, drivers, developers, and days, it gets easier and easier to make these kinds of mistakes. diff --git a/src/peripherals/borrowck.md b/src/peripherals/borrowck.md index 1bf439e..fd08449 100644 --- a/src/peripherals/borrowck.md +++ b/src/peripherals/borrowck.md @@ -1,7 +1,19 @@ -# The Borrow Checker +## Mutable Global State -Which, sounds suspiciously exactly like what the Borrow Checker does already! +Unfortunately, hardware is basically nothing but mutable global state, which can feel very frightening for a Rust developer. Hardware exists independently from the structures of the code we write, and can be modified at any time by the real world. + +## What should our rules be? + +How can we reliably interact with these peripherals? + +1. Always use `volatile` methods to read or write to peripheral memory, as it can change at any time +2. In software, ee should be able to share any number of read-only accesses to these peripherals +3. If some software should have read-write access to a peripheral, it should hold the only reference to that peripheral + +## The Borrow Checker + +The last two of these rules sound suspiciously exactly like what the Borrow Checker does already! Imagine if we could pass around ownership of these peripherals, or offer immutable or mutable references to them? -Well, we can, but for the Borrow Checker, we need to have exactly one instance of each peripheral, so Rust can handle this correctly. Well, luckliy in the hardware, there is only one instance of this specific serial port, but how can we expose that in code? \ No newline at end of file +Well, we can, but for the Borrow Checker, we need to have exactly one instance of each peripheral, so Rust can handle this correctly. Well, luckliy in the hardware, there is only one instance of any given peripheral, but how can we expose that in the structure of our code? \ No newline at end of file diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index f9edbe9..642a7c9 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -1,11 +1,10 @@ # Singletons > In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. +> +> *Wikipedia: [Singleton Pattern]* -https://en.wikipedia.org/wiki/Singleton_pattern - -* Old concept -* But how in Rust? +[Singleton Pattern]: https://en.wikipedia.org/wiki/Singleton_pattern ## But why can't we just use global variable(s)?