Skip to content

Isolate config register behaviour from misbehaving or confused software - #44

Merged
phsauter merged 4 commits into
pulp-platform:masterfrom
au-ts:sw-isolation
Sep 4, 2026
Merged

phsauter merged 4 commits into
pulp-platform:masterfrom
au-ts:sw-isolation

Conversation

@midnightveil

@midnightveil midnightveil commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Please review commit-by-commit.

The idea here is to make it easier to reason about the correctness of the config register-based flushing of the LLC, by saving a single copy of the CfgFlush and CfgSpm registers at the beginning of the flush. Without this, one has to reason that any TOCTOU issues are benign.

This was discussed previously with @niwis privately. One thing he raised was that it is generally assumed that trusted software has access to these registers, which is fair, although it does assume that software never makes any mistakes. My original proposal involved extra state being stored, but I've managed here to not require any more storage or flip-flops, just changing the time and ordering of when things are saved. I'd also argue that it's better to avoid software being able to corrupt hardware state, esp. with the issue with CfgSpm identified in one of the commits.

Appended onto the end of this changeset is a cleanup to remove a duplicate loop condition, which will have a visible timing impact: it will take one extra cycle following the final flush operation as we need an extra state transition. I personally think this is fine.

Hopefully this changes make it clearer how this code is expected to behave as well.


An alternative, possibly nicer solution, would be to have support in the register bus to allow "locking out" of software writes, then we can safely assume they won't change.

@phsauter

phsauter commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator
  1. config: clearly delineate software-writeble vs software-ro looks good (apart from the typo in the commit message title)
  2. config: use saved value of cfg_spm sw register in spm_lock_q looks good
  3. config: read the software-input CfgFlush register only once, I think this can still have a race if outstanding traffic keeps the FSM in FsmWaitAx (ie a second CfgSpm write can overwrite the first one without the first one ever doing something). Maybe this is fine (probably not, right?), a fix would need to add a bit of state to snapshot the spm on write or somehow make sure no second software write can get through until its done.
  4. config: cleanup duplicated loop condition I don't think the extra cycle is a problem.

@midnightveil

Copy link
Copy Markdown
Contributor Author

config: read the software-input CfgFlush register only once, I think this can still have a race if outstanding traffic keeps the FSM in FsmWaitAx (ie a second CfgSpm write can overwrite the first one without the first one ever doing something). Maybe this is fine (probably not, right?), a fix would need to add a bit of state to snapshot the spm on write or somehow make sure no second software write can get through until its done.

Only writes to commit cause movement, not just writes to spm or flush.

Yes, we can snapshot cfg_spm a while later than cfg_flushvif there's traffic that keeps in WaitAux. There will still only be one write to spm_lock, though: FssmWaitAux only captures cfg_spm in the same cycle it transitions to FsmInitFlush?

I'm happy to add some extra state to copy that immediately if desired, but I don't think it's necessary here.

My thought process was that as long as we only read it once, software can't mess up our internal state tracking.

The issue I'm trying to avoid is software corrupting our internal state, not software giving itself race conditions about which value ends up being used.

This does not change the behaviour. Also, because the HW we does
not affect whether a RW register can be written to be SW (and in
fact SW takes higher priority over HW if both try), let's just
make all the _en bits default-disabled and only enable when needed.
Otherwise, software can manipulate the value of the cfg_spm register
and cause the hardware to indicate that a way is flushed via the
`conf_regs_o.flushed` register (then used by other parts of the
hardware) when the actual way was never flushed and contains
dirty data. This is under the assumption that software writes
to the configuration registers after CommitCfg.
Previously, software could write to the register and every time
we went through FsmInitFlush it would update the to_flush_d list.
I believe that it was *fine*, in the sense that the list of
thought to be 'flushed' ways would remain coherent with what was
actually flushed no matter what software did.

However, this is quite hard to reason about, and there's basically
no reason why you would ever want to do this: you also don't expect
that anything done after writing the CommitCfg register would take
effect.
Previously, the loop condition for the FsmInitFlush case was
duplicated in the FsmEndFlush case. This meant an extra set of
the same code needed to be maintained.

The tradeoff is that we take one extra cycle to finish clearing
the flush on the last iteration. However, we've spent some large
amount of cycles flushing all the cache lines to memory, so I
don't think this is a big difference.
@midnightveil

Copy link
Copy Markdown
Contributor Author

(Pushed to fix commit typo)

@phsauter

phsauter commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Makes sense, then I misunderstood the goal.
In that case it looks good.

@phsauter
phsauter merged commit f8ca916 into pulp-platform:master Sep 4, 2026
9 of 10 checks passed
@midnightveil
midnightveil deleted the sw-isolation branch September 4, 2026 22:42
ricted98 pushed a commit that referenced this pull request Oct 9, 2026
* config: clearly delineate software-writable vs software-ro

This does not change the behaviour. Also, because the HW we does
not affect whether a RW register can be written to be SW (and in
fact SW takes higher priority over HW if both try), let's just
make all the _en bits default-disabled and only enable when needed.

* config: use 'saved' value of cfg_spm sw register in 'spm_lock_q'

Otherwise, software can manipulate the value of the cfg_spm register
and cause the hardware to indicate that a way is flushed via the
`conf_regs_o.flushed` register (then used by other parts of the
hardware) when the actual way was never flushed and contains
dirty data. This is under the assumption that software writes
to the configuration registers after CommitCfg.

* config: read the software-input CfgFlush register only once

Previously, software could write to the register and every time
we went through FsmInitFlush it would update the to_flush_d list.
I believe that it was *fine*, in the sense that the list of
thought to be 'flushed' ways would remain coherent with what was
actually flushed no matter what software did.

However, this is quite hard to reason about, and there's basically
no reason why you would ever want to do this: you also don't expect
that anything done after writing the CommitCfg register would take
effect.

* config: cleanup duplicated loop condition

Previously, the loop condition for the FsmInitFlush case was
duplicated in the FsmEndFlush case. This meant an extra set of
the same code needed to be maintained.

The tradeoff is that we take one extra cycle to finish clearing
the flush on the last iteration. However, we've spent some large
amount of cycles flushing all the cache lines to memory, so I
don't think this is a big difference.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants