Second pass at TC documentation updates

This commit is contained in:
Nav
2024-09-12 22:57:55 +01:00
parent 585519a1e5
commit 723ae4e76d

View File

@@ -5,6 +5,13 @@ user-space device drivers takes place here. All interactions with the connected
TargetController. It runs on a dedicated thread (see `Applciation::startTargetController()`). The source code for the TargetController. It runs on a dedicated thread (see `Applciation::startTargetController()`). The source code for the
TargetController component can be found in src/TargetController. The entry point is `TargetControllerComponent::run()`. TargetController component can be found in src/TargetController. The entry point is `TargetControllerComponent::run()`.
### Lifetime
The TargetController is the second (after the SignalHandler) component to be instantiated at startup, and the second
to last to be destroyed when Bloom shuts down.
The TargetController will outlive both the DebugServer and the Insight GUI components.
### Interfacing with the TargetController - The command-response mechanism ### Interfacing with the TargetController - The command-response mechanism
Other components within Bloom can interface with the TargetController via the provided command-response mechanism. Other components within Bloom can interface with the TargetController via the provided command-response mechanism.
@@ -146,6 +153,88 @@ auto tcService = Services::TargetControllerService{};
tcService.readMemory(...); // Will not be part of the atomic session tcService.readMemory(...); // Will not be part of the atomic session
``` ```
### Obtaining information on the connected target - target descriptor objects
Most components in Bloom require access to some target information. This information could be related to address spaces,
peripherals, registers, or any other target entity.
In Bloom, target information is held in descriptor objects. These objects are instances of descriptor structs intended
to hold information on a particular target entity. For example, information on a particular address space can be found
in the relevant `TargetAddressSpaceDescriptor` object.
All descriptor structs can be found in [src/Targets/](../Targets).
The `TargetDescriptor` struct is the top-level descriptor struct. Instances of all other descriptor structs can be
accessed via the `TargetDescriptor` struct.
The `GetTargetDescriptor` command returns a const reference to the top-level target descriptor object.
The `TargetControllerService::getTargetDescriptor()` member function can be used to obtain the top-level target
descriptor:
```c++
const auto& targetDescriptor = tcService.getTargetDescriptor();
// Obtain info on the target's "data" address space
const auto& dataAsDescriptor = targetDescriptor.getAddressSpaceDescriptor("data");
/**
* - dataAsDescriptor.addressRange.startAddress <- the start address of the address space
* - dataAsDescriptor.size() <- the size of the address space, in bytes
* - dataAsDescriptor.endianness <- the endianness of the address space
*
* See the TargetAddressSpaceDescriptor struct for more...
*/
// Obtain info on the target's "ADC0" peripheral
const auto& adcPerDescriptor = targetDescriptor.getPeripheralDescriptor("adc0");
/**
* - adcPerDescriptor.name <- the name of the peripheral
* - adcPerDescriptor.getRegisterGroupDescriptor(...) <- Access a register group within the peripheral
*
* See the TargetPeripheralDescriptor struct for more...
*/
```
The copying of descriptor objects is discouraged for two reasons:
- Descriptor objects hold a lot of information, making them expensive to copy
- Descriptor objects can be passed back to the TargetController, in the form of const references, when performing an
operation on a particular target entity. If the copied object does not outlive the TargetController command, the
TargetController may attempt to access a dangling reference, resulting in undefined behaviour. For more on this, see
the [Passing descriptor objects to the TargetController](#passing-descriptor-objects-to-the-targetController) section
below
The copy constructors and the assignment operators are deleted on all descriptor structs.
#### Passing descriptor objects to the TargetController
For some operations, the TargetController may require the referencing of a particular target entity. For example, to
read memory from the target, the TargetController will need to know which address space and memory segment to read
from. This is why the `ReadTargetMemory` command requires a `const TargetAddressSpaceDescriptor&` and
`const TargetMemorySegmentDescriptor&`. There are many commands that require const references to target descriptor
objects.
It is the invoker's responsibility to ensure that all const references to descriptor objects, passed to the
TargetController, are still valid at the time the TargetController services the command. If the TargetController
encounters a dangling reference, undefined behaviour will occur. See the "master target descriptor" for more.
#### Thread-safe access of the target descriptor - the master target descriptor object
The "master" target descriptor object is simply an instance of the top-level `TargetDescriptor` struct, owned and
managed by the TargetController. The TargetController will return a const reference of the master target descriptor
when servicing the `GetTargetDescriptor` command.
The TargetController will never modify the master target descriptor after creation. There are no mutable members in
any of the target descriptor structs. This means we can access a const reference of the descriptor, from any thread
within Bloom, assuming the thread does not outlive the TargetController.
Because the master target descriptor is owned by the TargetController, it, along with any of its contained objects,
can be passed safely to the TargetController, in any TargetController command.
Both the debug server and Insight GUI components hold a const reference of the master target descriptor, allowing them
to access target information freely.
Do **not** modify the master target descriptor. Do **not** perform a `const_cast` on the const reference. Ever.
### Target state observation ### Target state observation
The TargetController provides access to the target's current state, via the `GetTargetState` command, which will return The TargetController provides access to the target's current state, via the `GetTargetState` command, which will return
@@ -162,18 +251,18 @@ if (targetState.executionState == TargetExecutionState::STOPPED) {
} }
``` ```
#### Real-time, on-demand, thread-safe access to the target's current state - the master `TargetState` object #### Real-time, on-demand, thread-safe access to the target's current state - the master target state object
All members of the `TargetState` struct are accessible via atomic operations (that is, all members are of `std::atomic<...>` All members of the `TargetState` struct are accessible via atomic operations (that is, all members are of `std::atomic<...>`
type). This means that we can access a single instance of the `TargetState` struct across multiple threads, in a type). This means that we can access a single instance of the `TargetState` struct across multiple threads, in a
thread-safe manner. thread-safe manner.
The "master" `TargetState` object is simply an instance of the `TargetState` struct that is owned and managed by the The "master" target state object is simply an instance of the `TargetState` struct that is owned and managed by the
TargetController (`TargetControllerComponent::targetState`). It holds the current state of the target, at all times. TargetController (`TargetControllerComponent::targetState`). It holds the current state of the target, at all times.
When servicing the `GetTargetState` command, the TargetController returns a const reference to the master `TargetState` When servicing the `GetTargetState` command, the TargetController returns a const reference to the master target state
object. This means that, if the caller of `TargetControllerService::getTargetState()` needs real-time, on-demand access object. This means that, if the caller of `TargetControllerService::getTargetState()` needs real-time, on-demand access
to the target's current state, it can gain this by simply accepting a const reference of the master `TargetState` to the target's current state, it can gain this by simply accepting a const reference of the master target state
object: object:
```c++ ```c++
@@ -194,17 +283,20 @@ if (targetState.executionState == TargetExecutionState::STOPPED) {
tcService.resumeTargetExecution(); tcService.resumeTargetExecution();
/* /*
* At this point, targetState.executionState == TargetExecutionState::RUNNING, because the master TargetState * At this point, targetState.executionState == TargetExecutionState::RUNNING, because the master target state
* object, which `targetState` references, will have been updated by the TargetController (as a result of the call * object, which `targetState` references, will have been updated by the TargetController (as a result of the call
* to `tcService.resumeTargetExecution()` above). * to `tcService.resumeTargetExecution()` above).
*
* This, of course, assumes that the target is still running by the time we get to this assertion...
*/ */
assert(targetState.executionState == TargetExecutionState::RUNNING);
} }
``` ```
The master `TargetState` object can be accessed freely by any other component within Bloom, just as long as the The master target state object can be accessed freely by any other component within Bloom, just as long as the
component doesn't outlive the TargetController (at the time of writing this, no component outlives the TargetController). component doesn't outlive the TargetController.
Many Insight GUI widgets make use of the master `TargetState` object, as it allows for immediate access to the target's Many Insight GUI widgets make use of the master target state object, as it allows for immediate access to the target's
current state, without having to bother the TargetController via an InsightWorker task. current state, without having to bother the TargetController via an InsightWorker task.
#### Target state changed events #### Target state changed events