diff options
author | Russell Currey <ruscur@russell.cc> | 2017-06-24 14:17:12 -0500 |
---|---|---|
committer | Stewart Smith <stewart@linux.vnet.ibm.com> | 2017-06-26 14:28:58 +1000 |
commit | 85fa2929bd9fceef51e550d7e7d6811cbf0d8295 (patch) | |
tree | f53eb18a1604bf8e430cba5e13b6881ccda450ea /hw/phb4.c | |
parent | c40c9c75dfaefba227af9c4b5c03277e6d684690 (diff) | |
download | skiboot-85fa2929bd9fceef51e550d7e7d6811cbf0d8295.zip skiboot-85fa2929bd9fceef51e550d7e7d6811cbf0d8295.tar.gz skiboot-85fa2929bd9fceef51e550d7e7d6811cbf0d8295.tar.bz2 |
phb4: Mask out write-1-to-clear registers in RC cfg
The root complex config space only supports 4-byte accesses. Thus, when
the client requests a smaller size write, we do a read-modify-write to
the register.
However, some register have bits defined as "write 1 to clear".
If we do a RMW cycles on such a register and such bits are 1 in the
part that the client doesn't intend to modify, we will accidentally
write back those 1's and clear the corresponding bit.
This avoids it by masking out those magic bits from the "old" value
read from the register.
Signed-off-by: Russell Currey <ruscur@russell.cc>
Signed-off-by: Michael Neuling <mikey@neuling.org>
Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
Diffstat (limited to 'hw/phb4.c')
-rw-r--r-- | hw/phb4.c | 44 |
1 files changed, 39 insertions, 5 deletions
@@ -264,20 +264,54 @@ static int64_t phb4_rc_write(struct phb4 *p, uint32_t offset, uint8_t sz, uint32_t val) { uint32_t reg = offset & ~3; - uint32_t old, mask, shift; + uint32_t old, mask, shift, oldold; int64_t rc; if (reg > PHB_RC_CONFIG_SIZE) return OPAL_SUCCESS; - /* If size isn't 4-bytes, do a RMW cycle - * - * XXX TODO: Filter out registers that do write-1-to-clear !!! - */ + /* If size isn't 4-bytes, do a RMW cycle */ if (sz < 4) { rc = phb4_rc_read(p, reg, 4, &old); if (rc != OPAL_SUCCESS) return rc; + + /* + * Since we have to Read-Modify-Write here, we need to filter + * out registers that have write-1-to-clear bits to prevent + * clearing stuff we shouldn't be. So for any register this + * applies to, mask out those bits. + */ + oldold = old; + switch(reg) { + case 0x1C: /* Secondary status */ + old &= 0x00ffffff; /* mask out 24-31 */ + break; + case 0x50: /* EC - Device status */ + old &= 0xfff0ffff; /* mask out 16-19 */ + break; + case 0x58: /* EC - Link status */ + old &= 0x3fffffff; /* mask out 30-31 */ + break; + case 0x78: /* EC - Link status 2 */ + old &= 0xf000ffff; /* mask out 16-27 */ + break; + /* These registers *only* have write-1-to-clear bits */ + case 0x104: /* AER - Uncorr. error status */ + case 0x110: /* AER - Corr. error status */ + case 0x130: /* AER - Root error status */ + case 0x180: /* P16 - status */ + case 0x184: /* P16 - LDPM status */ + case 0x188: /* P16 - FRDPM status */ + case 0x18C: /* P16 - SRDPM status */ + old &= 0x00000000; + break; + } + + if (old != oldold) + PHBDBG(p, "Rewrote %x to %x for reg %x for W1C\n", + oldold, old, reg); + if (sz == 1) { shift = (offset & 3) << 3; mask = 0xff << shift; |