More thinking about interrupt priorities

This commit is contained in:
Luke Wren 2022-07-30 15:42:26 +01:00
parent 7946432d7a
commit c73c09a48a
2 changed files with 10390 additions and 6793 deletions

File diff suppressed because it is too large Load Diff

View File

@ -395,11 +395,7 @@ Address: `0x7b3`
Not implemented. Access will cause an illegal instruction exception.
=== Custom CSRs
These are all allocated in the space `0xbc0` through `0xbff` which is available for custom read/write M-mode CSRs, and `0xfc0` through `0xfff` which is available for custom read-only M-mode CSRs.
Hazard3 also allocates a custom _Debug Mode_ register <<reg-dmdata0>> in this space.
=== Custom Debug Mode CSRs
[[reg-dmdata0]]
==== dmdata0
@ -414,6 +410,8 @@ The Debug Module uses this mapping to exchange data with the core by injecting `
This CSR address is given by the `dataaddress` field of the Debug Module's `hartinfo` register, and `hartinfo.dataaccess` is set to 0 to indicate this is a CSR mapping, not a memory mapping.
=== Custom Interrupt Handling CSRs
[[reg-meie]]
==== meiea
@ -433,6 +431,13 @@ csrr a0, meiea // Read from window 0 (edge case)
The purpose of this scheme is to allow software to _index_ an array of interrupt enables (something not usually possible in the CSR space) without introducing a stateful CSR index register which may have to be saved/restored around IRQs.
[cols="10h,20h,~", options="header"]
|===
| Bits | Name | Description
| 31:16 | window | 16-bit read/write window into the external interrupt enable array
| 15:5 | - | RES0
| 4:0 | index | Write-only self-clearing field (no value is stored) used to control which window of the array appears in `window`.
|===
[[reg-meip]]
==== meipa
@ -443,7 +448,7 @@ External interrupt pending array. Contains a read-only bit for each external int
A `1` bit indicates that interrupt is currently asserted. IRQs are assumed to be level-sensitive, and the relevant `meipa` bit is cleared by servicing the requestor so that it deasserts its interrupt request.
When any interrupt of sufficient priority is both set in `meipa` and enabled in `meiea`, the standard RISC-V external interrupt pending bit `mip.meip` is asserted. In other words, `meip0` is filtered by `meiea` to generate the standard `mip.meip` flag. So, an external interrupt is taken when _all_ of the following are true:
When any interrupt of sufficient priority is both set in `meipa` and enabled in `meiea`, the standard RISC-V external interrupt pending bit `mip.meip` is asserted. In other words, `meipa` is filtered by `meiea` to generate the standard `mip.meip` flag. So, an external interrupt is taken when _all_ of the following are true:
* An interrupt is currently asserted in `meipa`
* The matching interrupt enable bit is set in `meiea`
@ -456,21 +461,58 @@ In this case, the processor jumps to either:
* `mtvec` directly, if vectoring is disabled (`mtvec[0]` is 0)
* `mtvec + 0x2c`, if vectoring is enabled (`mtvec[0]` is 1)
==== meipra
[cols="10h,20h,~", options="header"]
|===
| Bits | Name | Description
| 31:16 | window | 16-bit read-only window into the external interrupt pending array
| 15:5 | - | RES0
| 4:0 | index | Write-only, self-clearing field (no value is stored) used to control which window of the array appears in `window`.
|===
[[reg-meifa]]
==== meifa
Address: `0xbe2`
External interrupt force array. Contains a read-write bit for every interrupt request. Writing a 1 to a bit in the interrupt force array causes the corresponding bit to become pending in `meipa`. Software can use this feature to manually trigger a particular interrupt.
There are no restrictions on using `meifa` inside of an interrupt. The more useful case here is to schedule some lower-priority handler from within a high-priority interrupt, so that it will execute before the core returns to the foreground code. Implementers may wish to reserve some external IRQs with their external inputs tied to 0 for this purpose.
Bits can be cleared by software, and are cleared automatically by hardware upon a read of `meinext` which returns the corresponding IRQ number in `meinext.irq` (no matter whether `meinext.update` is written).
`meifa` implements the same array window indexing scheme as `meiea` and `meipa`.
[cols="10h,20h,~", options="header"]
|===
| Bits | Name | Description
| 31:16 | window | 16-bit read/write window into the external interrupt force array
| 15:5 | - | RES0
| 4:0 | index | Write-only, self-clearing field (no value is stored) used to control which window of the array appears in `window`.
|===
==== meipra
Address: `0xbe3`
External interrupt priority array. Each interrupt has an (up to) 4-bit priority value associated with it, and each access to this register reads and/or writes a 16-bit window containing four such priority values. When less than 16 priority levels are available, the LSBs of the priority fields are hardwired to 0.
When an interrupt's priority is lower than the current preemption priority `meicontext.preempt`, it is treated as not being pending. The pending bit in `meipa` will still assert, but the machine external interrupt pending bit `mip.meip` will not, so the processor will ignore this interrupt. See <<reg-meicontext>>.
[cols="10h,20h,~", options="header"]
|===
| Bits | Name | Description
| 31:16 | window | 16-bit read/write window into the external interrupt priority array, containing four 4-bit priority values.
| 15:7 | - | RES0
| 6:0 | index | Write-only, self-clearing field (no value is stored) used to control which window of the array appears in `window`.
|===
==== meinext
Address: `0xbe3`
Address: `0xbe4`
Get next interrupt. Contains the index of the highest-priority external interrupt which is both asserted in `meipa` and enabled in `meiea`, left-shifted by 2 so that it can be used to index an array of 32-bit function pointers. If there is no such interrupt, the MSB is set.
When multiple interrupts of the same priority are both pending and enabled, the lowest-numbered wins. Interrupts with priority less than `meicontext.preempt` are treated as though they are not pending.
When multiple interrupts of the same priority are both pending and enabled, the lowest-numbered wins. Interrupts with priority less than `meicontext.ppreempt` -- the _previous_ preemption priority -- are treated as though they are not pending. This is to ensure that a preempting interrupt frame does not service interrupts which may be in progress in the frame that was preempted.
[cols="10h,20h,~", options="header"]
|===
@ -485,20 +527,29 @@ When multiple interrupts of the same priority are both pending and enabled, the
[[reg-meicontext]]
==== meicontext
Address: `0xbe4`
Address: `0xbe5`
External interrupt context register. Configures the priority level for interrupt preemption, and helps software track which interrupt it is currently in. The latter is useful when a common interrupt service routine handles interrupt requests from multiple instances of the same peripheral.
Generally this register is updated by hardware on an `meinext` access, and saved/restored by software when entering/exiting an interrupt dispatch routine.
A three-level stack of preemption priorities is maintained in the `preempt`, `ppreempt` and `pppreempt` fields. The top entry of the priority stack, `preempt`, is used by hardware to ensure that only higher-priority interrupts can preempt the current interrupt. The next entry, `ppreempt`, is used to avoid servicing interrupts which may already be in progress in a frame that was preempted.
The third entry, `pppreempt`, has no hardware effect, but ensures that `preempt` and `ppreempt` correctly track the current interrupt frame across arbitrary levels of nested interrupts, so long as software saves/restores the `meicontext` register appropriately.
[cols="10h,20h,~", options="header"]
|===
| Bits | Name | Description
| 31:21 | - | RES0
| 20:16 | preempt | Minimum interrupt priority to preempt the current interrupt. Set to the priority of `meinext.irq`, plus one, when `meinext.update` is written. Interrupts with lower priority than `preempt` do not cause the core to transfer to an interrupt handler.
| 15 | noirq | Not in interrupt (read/write). Set to 1 at reset, and set to `meinext.noirq` when `meinext.update` is written.
| 14:9 | - | RES0
| 8:0 | irq | Current IRQ number (read/write). Set to `meinext.irq` when `meinext.update` is written..
| Bits | Name | Description
| 31:28 | pppreempt | Previous `ppreempt`. Set to `ppreempt` when taking an interrupt, set to zero by `mret`. Has no hardware effect, but ensures that when `meicontext` is saved/restored correctly, `preempt` and `ppreempt` stack correctly through arbitrarily many preemption frames.
TODO: how to track whether the `mret` is a return from external IRQ?
| 27:24 | ppreempt | Previous `preempt`. Set to `preempt` when taking an interrupt, restored to to `pppreempt` by `mret`. IRQs of lower priority than `preemptp` are not visible in `meinext`, so that the preempted interrupt is not re-taken in the preempting frame.
One bit smaller than `preempt`, because when `preempt` has its maximum value of 16, no further preemption is possible.
| 23:21 | - | RES0
| 20:16 | preempt | Minimum interrupt priority to preempt the current interrupt. Set to the priority of `meinext.irq`, plus one, when `meinext.update` is written. Interrupts with lower priority than `preempt` do not cause the core to transfer to an interrupt handler.
| 15:12 | - | RES0
| 11 | noirq | Not in interrupt (read/write). Set to 1 at reset or when taking any trap other than an external interrupt. Set to `meinext.noirq` when `meinext.update` is written.
| 10:9 | - | RES0
| 8:0 | irq | Current IRQ number (read/write). Set to `meinext.irq` when `meinext.update` is written..
|===
The following is an example of an external interrupt vector (`mip.meip`) which implements nested, prioritised interrupt dispatch using `meicontext` and `meinext`:
@ -557,6 +608,8 @@ no_more_irqs:
mret
----
=== Custom Power Control CSRs
==== msleep
Address: `0xbf0`