At times, it can be difficult or even impossible to configure all IOPMP settings when the system starts, especially before I/O agents are active or connected to the system. As a result, it is necessary to update IOPMP settings during runtime. This may occur when a device is enabled, disabled, added, removed, or when the accessible area of a device changes. When updating, it is important to avoid putting IOPMP in a transient metastable state due to incomplete settings. However, updating IOPMP settings often involves a series of control accesses, and if a transaction check occurs during the update, it can potentially create a vulnerability. It can be difficult for the security software to guarantee that no transactions are in progress from all related initiators. A false alarm could result in significant performance issues. This chapter describes an optional method for updating IOPMP’s settings without intervening transaction initiators.
The term here "stable" refers to meeting the atomicity requirement. This implies that when updating an IOPMP, all transactions from input ports must be checked either before any changes or after completing all changes. Essentially, using partial settings in an IOPMP should be avoided. The succeeding sections will describe the mechanism to satisfy this requirement.
The general approach to the atomicity requirement has three major steps, conceptually described as follows:
-
Step 1: Stall related transactions. Before proceeding with any updates, delay checking the transactions that may be impacted.
-
Step 2: Update IOPMP’s settings.
-
Step 3: Resume stalled transactions.
For step 1, it’s important to verify if the necessary stalling transactions have taken place since they might not be instantaneous in certain implementations. Following this, execute the IOPMP update as step 2, and finally, resume all stalled transactions in step 3.
Note
|
In some cases, Step 1 and Step 3 may be skipped as long as no transaction check can interrupt Step 2. Updating MDs associated with a specific RRID to other MDs is an example. |
Note
|
While stalling transactions have taken place in Step 1, the IOPMP delays checking the stalled transactions until the IOPMP resumes the stalled transactions. For example, the IOPMP may wait the stalled transactions and/or respond retry messages to transaction requestors for the stalled transactions. |
For Step 1, it’s possible to postpone all transactions until all updates are finished. However, this could cause unrelated transactions to experience unnecessary delays. This might not be tolerable for devices that require low latency, like a display controller that periodically retrieves a frame from its video buffer. This section explains the mechanism that only stalls specific transactions to prevent the aforementioned scenario and ensure the atomicity requirement. All the features mentioned below are optional.
Since the stalls occur when updating is in progress, determining whether a transaction’s check should wait cannot be based on any IOPMP’s configuration about to change. Therefore, the only information that can be relied upon for this decision is the RRID carried by the transaction. To simplify the following description, we use a conceptual signal called rrid_stall[s] to indicate whether the transaction with RRID=s must wait. Please note that it may not be an actual signal in practice and is not accessible directly for software.
A conceptual internal signal rrid_stall has the same number of bits as the RRIDs in the IOPMP. rrid_stall is generated by the bit MDSTALL.exempt. stall_by_md is the concatenation of MDSTALL.mdh and MDSTALL.md, that is, stall_by_md[30:0] is MDSTALL.md[31:1] and stall_by_md[62:31] is MDSTALLH.mdh[31:0] if any. When MDSTALL.exempt is zero, any non-zero value in stall_by_md[s] will cause transactions with RRID=s to be stalled for all RRID s associated with MD m. On the contrary, on MDSTALL.exempt=1, checks of all transactions must wait except those with RRID=s associated with any MD m and stall_by_md[m] = 1. This relation can be more precisely described as follows:
rrid_stall[s] ⇐ MDSTALL.exempt ^ ( Reduction_OR (SRCMD(s).md & stall_by_md));
As to SRCMD table Format 2, SRCMD(s).md[m] in the above equation is: ‘0’ for all an umimplemented memory domain m and ‘1’ for an implemented memory domain m because every RRID associates all implemented MDs.
For any unimplemented memory domain, the corresponding bit in MDSTALL.md or MDSTALLH.mdh should be wired to 0.
rrid_stall should be captured only when MDSTALL.exempt is written, that is, when MDSTALL is written. When MDSTALLH is written, the only action is to hold the value.
Note
|
Although rrid_stall is related to SRCMD table, but should be captured only when MDSTALL.exempt is written. The behavior of writing MDSTALL is used to capture a momentary snapshot of the table because the table may not be stable during the updating. |
Note
|
When writing MDSTALL, the specification only defines the transactions with RRID=i must wait for all rrid_stall[i]=1. It doesn’t request the rest of the transactions to stall or not. It only asks that all transactions be resumed when writing MDSTALL with a zero. |
If MDSTALL doesn’t stall all the desired transactions, there is an optional method to pick the transaction with specific RRIDs. The RRIDSCP register comprises two fields: a 2-bit RRIDSCP.op and a field for RRIDSCP.rrid. By setting RRIDSCP.op=1, the rrid_stall[i] is activated for i=RRIDSCP.rrid. Conversely, by setting RRIDSCP.op=2, the rrid_stall[i] is deactivated for i=RRIDSCP.rrid. This register is used to fine-tune the result of writing MDSTALL. The value of RRIDSCP.op=0 is to query the rrid_stall indirectly, and the value of 3 is reserved.
In order to resume all stalled transactions, the IOPMP can be prompted by writing 0 to MDSTALL. This corresponds to Step 3 of the "Programming Steps" section. After MDSTALL is written by zero, an IOPMP should de-assert MDSTALL.is_stalled within some time, at which point all transactions have been resumed.
In Step 1 of programming IOPMP, MDSTALL can be written at most once and before any RRIDSCP is written. After a resume, writing a non-zero value to MDSTALL multiple times leads to an undefined situation.
RRIDSCP can be written multiple times or not at all. To determine whether all requested stalls take effect, one can read back the bit MDSTALL.is_stalled, which is in the same location as MDSTALL.exempt on a write. MDSTALL.is_stalled=1 indicates all requested stalls taking effect after the last writing MDSTALL (included) plus any following writing RRIDSCP.
Note
|
After writing any non-zero value to MDSTALL, MDSTALL.is_stalled must be asserted within some time, no matter whether any RRID is stalled. The software polling the status bit doesn’t need to consider whether any RRID will be stalled. On the other hand, after writing zero to MDSTALLH (if any) and then MDSTALL, MDSTALL.is_stalled must be de-asserted within some time. |
Based on the aforementioned, complete steps to program an IOPMP are suggested.
-
Step 1.1: write MDSTALL once // exactly once
-
Step 1.2: write RRIDSCP zero or more times
-
Step 1.3: poll until MDSTALL.is_stalled == 1 // to ensure all stalls takes effect
-
Step 2: update IOPMP’s configuration
-
Step 3.1: write MDSTALL=0 // resume all transactions
-
Step 3.2: poll until MDSTALL.is_stalled == 0 // optional, to ensure all resumes take effect.
Some steps may be skipped according to the actual implementation.
To query if all transactions associated with a specific RRID are stalled, do the following. First, write 0 to RRIDSCP.op and the RRID you want to query to RRIDSCP.rrid. Then, read back RRIDSCP. The readback of RRIDSCP.stat = 1 means that transactions with the queried RRID have stalled, that is, the corresponding bit in rrid_stall is 1. If the value is 2, it means they are not stalled. A value of 3 indicates an unimplemented or unselectable RRID in RRIDSCP.rrid. RRIDSCP.stat is in the same location as RRIDSCP.op on a write. RRIDSCP.rrid should keep the last written legal RRID and RRIDSCP.stat reflects the current state of this RRID. This method is considered an indirect way to read rrid_stall.
Note
|
In certain implementations, rather than stalling the related transactions, the system may opt to fault the checking transactions during an IOPMP atomic update. The procedure for faulting checking transactions is identical to Steps 1-3 mentioned above, except that, instead of stalling and delaying the transactions, transactions will be faulted and cannot be resumed. Faulting transactions can be advantageous if the system lacks sufficient buffer capacity to record and store all transactions during the IOPMP programming process. To select faulting over stalling, one should set ERR_CFG.stall_violation_en to 1. If any transaction is faulted due to the stalled transactions, the error information shall be logged in ERR_REQINFO, where ERR_REQINFO.etype = 0x7 (error due to stalled transactions). |
All registers described in this chapter are optional. Moreover, these features could be partially implemented. In MDSTALL.md and MDSTALLH.mdh, not every bit should be implemented even though the corresponding MD is implemented. An unimplemented bit means unselectable and should be wired to zero. To test which bits are implemented, one can write all 1’s to MDSTALL.md and MDSTALLH.mdh and then read them back. An implemented bit returns 1.
If an IOPMP implementation has fewer than 32 memory domains, MDSTALLH should be wired to zero.
Note
|
An example of partial implementation of MDSTALL.md/MDSTALLH.mdh is a system with a display controller, which is a latency-sensitive device. On updating the IOPMP, the transactions initiated from the display controller should not be stalled. Thus, one can always use MDSTALL.exempt=1 and MDSTALL.md[j]=1, where MD j is the memory domain for the frame buffer that the display controller keeps accessing. Thus, the system only needs to implement MDSTALL.md[j]. |
If whole MDSTALL is not implemented, MDSTALL and MDSTALLH should always return zero.
If RRIDSCP is not implemented, it always returns zero. One can test if it is implemented by writing a zero and then reading it back. Any IOPMP implementing RRIDSCP should not return a zero in RRIDSCP.stat in this case.
It is unnecessary to allow every implemented RRID to be selectable by RRIDSCP.rrid. If an unimplemented or unselectable RRID is written into RRIDSCP.rrid, it returns RRIDSCP.stat = 3.