aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell Currey <ruscur@russell.cc>2017-06-24 14:17:12 -0500
committerStewart Smith <stewart@linux.vnet.ibm.com>2017-06-26 14:28:58 +1000
commit85fa2929bd9fceef51e550d7e7d6811cbf0d8295 (patch)
treef53eb18a1604bf8e430cba5e13b6881ccda450ea
parentc40c9c75dfaefba227af9c4b5c03277e6d684690 (diff)
downloadskiboot-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>
-rw-r--r--hw/phb4.c44
1 files changed, 39 insertions, 5 deletions
diff --git a/hw/phb4.c b/hw/phb4.c
index 3ee618e..7e8ba3b 100644
--- a/hw/phb4.c
+++ b/hw/phb4.c
@@ -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;