xref: /openbmc/qemu/hw/pci/shpc.c (revision fe6d4434d2531f8bc6fffc7878c801e8d8190c5d)
197d5408fSPeter Maydell #include "qemu/osdep.h"
2da34e65cSMarkus Armbruster #include "qapi/error.h"
337e626ceSYuval Shaia #include "qemu/host-utils.h"
41de7afc9SPaolo Bonzini #include "qemu/range.h"
5b4a42f81SPaolo Bonzini #include "qemu/error-report.h"
6c759b24fSMichael S. Tsirkin #include "hw/pci/shpc.h"
7ca77ee28SMarkus Armbruster #include "migration/qemu-file-types.h"
8c759b24fSMichael S. Tsirkin #include "hw/pci/pci.h"
906aac7bdSMichael S. Tsirkin #include "hw/pci/pci_bus.h"
10c759b24fSMichael S. Tsirkin #include "hw/pci/msi.h"
11*86f0aa1dSVladimir Sementsov-Ogievskiy #include "trace.h"
12315a1350SMichael S. Tsirkin 
13315a1350SMichael S. Tsirkin /* TODO: model power only and disabled slot states. */
14315a1350SMichael S. Tsirkin /* TODO: handle SERR and wakeups */
15315a1350SMichael S. Tsirkin /* TODO: consider enabling 66MHz support */
16315a1350SMichael S. Tsirkin 
17315a1350SMichael S. Tsirkin /* TODO: remove fully only on state DISABLED and LED off.
18315a1350SMichael S. Tsirkin  * track state to properly record this. */
19315a1350SMichael S. Tsirkin 
20315a1350SMichael S. Tsirkin /* SHPC Working Register Set */
21315a1350SMichael S. Tsirkin #define SHPC_BASE_OFFSET  0x00 /* 4 bytes */
22315a1350SMichael S. Tsirkin #define SHPC_SLOTS_33     0x04 /* 4 bytes. Also encodes PCI-X slots. */
23315a1350SMichael S. Tsirkin #define SHPC_SLOTS_66     0x08 /* 4 bytes. */
24315a1350SMichael S. Tsirkin #define SHPC_NSLOTS       0x0C /* 1 byte */
25315a1350SMichael S. Tsirkin #define SHPC_FIRST_DEV    0x0D /* 1 byte */
26315a1350SMichael S. Tsirkin #define SHPC_PHYS_SLOT    0x0E /* 2 byte */
27315a1350SMichael S. Tsirkin #define SHPC_PHYS_NUM_MAX 0x7ff
28315a1350SMichael S. Tsirkin #define SHPC_PHYS_NUM_UP  0x2000
29315a1350SMichael S. Tsirkin #define SHPC_PHYS_MRL     0x4000
30315a1350SMichael S. Tsirkin #define SHPC_PHYS_BUTTON  0x8000
31315a1350SMichael S. Tsirkin #define SHPC_SEC_BUS      0x10 /* 2 bytes */
32315a1350SMichael S. Tsirkin #define SHPC_SEC_BUS_33   0x0
33315a1350SMichael S. Tsirkin #define SHPC_SEC_BUS_66   0x1 /* Unused */
34315a1350SMichael S. Tsirkin #define SHPC_SEC_BUS_MASK 0x7
35315a1350SMichael S. Tsirkin #define SHPC_MSI_CTL      0x12 /* 1 byte */
36315a1350SMichael S. Tsirkin #define SHPC_PROG_IFC     0x13 /* 1 byte */
37315a1350SMichael S. Tsirkin #define SHPC_PROG_IFC_1_0 0x1
38315a1350SMichael S. Tsirkin #define SHPC_CMD_CODE     0x14 /* 1 byte */
39315a1350SMichael S. Tsirkin #define SHPC_CMD_TRGT     0x15 /* 1 byte */
40315a1350SMichael S. Tsirkin #define SHPC_CMD_TRGT_MIN 0x1
41315a1350SMichael S. Tsirkin #define SHPC_CMD_TRGT_MAX 0x1f
42315a1350SMichael S. Tsirkin #define SHPC_CMD_STATUS   0x16 /* 2 bytes */
43315a1350SMichael S. Tsirkin #define SHPC_CMD_STATUS_BUSY          0x1
44315a1350SMichael S. Tsirkin #define SHPC_CMD_STATUS_MRL_OPEN      0x2
45315a1350SMichael S. Tsirkin #define SHPC_CMD_STATUS_INVALID_CMD   0x4
46315a1350SMichael S. Tsirkin #define SHPC_CMD_STATUS_INVALID_MODE  0x8
47315a1350SMichael S. Tsirkin #define SHPC_INT_LOCATOR  0x18 /* 4 bytes */
48315a1350SMichael S. Tsirkin #define SHPC_INT_COMMAND  0x1
49315a1350SMichael S. Tsirkin #define SHPC_SERR_LOCATOR 0x1C /* 4 bytes */
50315a1350SMichael S. Tsirkin #define SHPC_SERR_INT     0x20 /* 4 bytes */
51315a1350SMichael S. Tsirkin #define SHPC_INT_DIS      0x1
52315a1350SMichael S. Tsirkin #define SHPC_SERR_DIS     0x2
53315a1350SMichael S. Tsirkin #define SHPC_CMD_INT_DIS  0x4
54315a1350SMichael S. Tsirkin #define SHPC_ARB_SERR_DIS 0x8
55315a1350SMichael S. Tsirkin #define SHPC_CMD_DETECTED 0x10000
56315a1350SMichael S. Tsirkin #define SHPC_ARB_DETECTED 0x20000
57315a1350SMichael S. Tsirkin  /* 4 bytes * slot # (start from 0) */
58315a1350SMichael S. Tsirkin #define SHPC_SLOT_REG(s)         (0x24 + (s) * 4)
59315a1350SMichael S. Tsirkin  /* 2 bytes */
60315a1350SMichael S. Tsirkin #define SHPC_SLOT_STATUS(s)       (0x0 + SHPC_SLOT_REG(s))
61315a1350SMichael S. Tsirkin 
62315a1350SMichael S. Tsirkin /* Same slot state masks are used for command and status registers */
63315a1350SMichael S. Tsirkin #define SHPC_SLOT_STATE_MASK     0x03
64315a1350SMichael S. Tsirkin #define SHPC_SLOT_STATE_SHIFT \
65786a4ea8SStefan Hajnoczi     ctz32(SHPC_SLOT_STATE_MASK)
66315a1350SMichael S. Tsirkin 
67315a1350SMichael S. Tsirkin #define SHPC_STATE_NO       0x0
68315a1350SMichael S. Tsirkin #define SHPC_STATE_PWRONLY  0x1
69315a1350SMichael S. Tsirkin #define SHPC_STATE_ENABLED  0x2
70315a1350SMichael S. Tsirkin #define SHPC_STATE_DISABLED 0x3
71315a1350SMichael S. Tsirkin 
72315a1350SMichael S. Tsirkin #define SHPC_SLOT_PWR_LED_MASK   0xC
73315a1350SMichael S. Tsirkin #define SHPC_SLOT_PWR_LED_SHIFT \
74786a4ea8SStefan Hajnoczi     ctz32(SHPC_SLOT_PWR_LED_MASK)
75315a1350SMichael S. Tsirkin #define SHPC_SLOT_ATTN_LED_MASK  0x30
76315a1350SMichael S. Tsirkin #define SHPC_SLOT_ATTN_LED_SHIFT \
77786a4ea8SStefan Hajnoczi     ctz32(SHPC_SLOT_ATTN_LED_MASK)
78315a1350SMichael S. Tsirkin 
79315a1350SMichael S. Tsirkin #define SHPC_LED_NO     0x0
80315a1350SMichael S. Tsirkin #define SHPC_LED_ON     0x1
81315a1350SMichael S. Tsirkin #define SHPC_LED_BLINK  0x2
82315a1350SMichael S. Tsirkin #define SHPC_LED_OFF    0x3
83315a1350SMichael S. Tsirkin 
84315a1350SMichael S. Tsirkin #define SHPC_SLOT_STATUS_PWR_FAULT      0x40
85315a1350SMichael S. Tsirkin #define SHPC_SLOT_STATUS_BUTTON         0x80
86315a1350SMichael S. Tsirkin #define SHPC_SLOT_STATUS_MRL_OPEN       0x100
87315a1350SMichael S. Tsirkin #define SHPC_SLOT_STATUS_66             0x200
88315a1350SMichael S. Tsirkin #define SHPC_SLOT_STATUS_PRSNT_MASK     0xC00
89315a1350SMichael S. Tsirkin #define SHPC_SLOT_STATUS_PRSNT_EMPTY    0x3
90315a1350SMichael S. Tsirkin #define SHPC_SLOT_STATUS_PRSNT_25W      0x1
91315a1350SMichael S. Tsirkin #define SHPC_SLOT_STATUS_PRSNT_15W      0x2
92315a1350SMichael S. Tsirkin #define SHPC_SLOT_STATUS_PRSNT_7_5W     0x0
93315a1350SMichael S. Tsirkin 
94315a1350SMichael S. Tsirkin #define SHPC_SLOT_STATUS_PRSNT_PCIX     0x3000
95315a1350SMichael S. Tsirkin 
96315a1350SMichael S. Tsirkin 
97315a1350SMichael S. Tsirkin  /* 1 byte */
98315a1350SMichael S. Tsirkin #define SHPC_SLOT_EVENT_LATCH(s)        (0x2 + SHPC_SLOT_REG(s))
99315a1350SMichael S. Tsirkin  /* 1 byte */
100315a1350SMichael S. Tsirkin #define SHPC_SLOT_EVENT_SERR_INT_DIS(d, s) (0x3 + SHPC_SLOT_REG(s))
101315a1350SMichael S. Tsirkin #define SHPC_SLOT_EVENT_PRESENCE        0x01
102315a1350SMichael S. Tsirkin #define SHPC_SLOT_EVENT_ISOLATED_FAULT  0x02
103315a1350SMichael S. Tsirkin #define SHPC_SLOT_EVENT_BUTTON          0x04
104315a1350SMichael S. Tsirkin #define SHPC_SLOT_EVENT_MRL             0x08
105315a1350SMichael S. Tsirkin #define SHPC_SLOT_EVENT_CONNECTED_FAULT 0x10
106315a1350SMichael S. Tsirkin /* Bits below are used for Serr/Int disable only */
107315a1350SMichael S. Tsirkin #define SHPC_SLOT_EVENT_MRL_SERR_DIS    0x20
108315a1350SMichael S. Tsirkin #define SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS 0x40
109315a1350SMichael S. Tsirkin 
110315a1350SMichael S. Tsirkin #define SHPC_MIN_SLOTS        1
111315a1350SMichael S. Tsirkin #define SHPC_MAX_SLOTS        31
112315a1350SMichael S. Tsirkin #define SHPC_SIZEOF(d)    SHPC_SLOT_REG((d)->shpc->nslots)
113315a1350SMichael S. Tsirkin 
114315a1350SMichael S. Tsirkin /* SHPC Slot identifiers */
115315a1350SMichael S. Tsirkin 
116315a1350SMichael S. Tsirkin /* Hotplug supported at 31 slots out of the total 32.  We reserve slot 0,
117315a1350SMichael S. Tsirkin    and give the rest of them physical *and* pci numbers starting from 1, so
118315a1350SMichael S. Tsirkin    they match logical numbers.  Note: this means that multiple slots must have
119315a1350SMichael S. Tsirkin    different chassis number values, to make chassis+physical slot unique.
120315a1350SMichael S. Tsirkin    TODO: make this configurable? */
121315a1350SMichael S. Tsirkin #define SHPC_IDX_TO_LOGICAL(slot) ((slot) + 1)
122315a1350SMichael S. Tsirkin #define SHPC_LOGICAL_TO_IDX(target) ((target) - 1)
123315a1350SMichael S. Tsirkin #define SHPC_IDX_TO_PCI(slot) ((slot) + 1)
124315a1350SMichael S. Tsirkin #define SHPC_PCI_TO_IDX(pci_slot) ((pci_slot) - 1)
125315a1350SMichael S. Tsirkin #define SHPC_IDX_TO_PHYSICAL(slot) ((slot) + 1)
126315a1350SMichael S. Tsirkin 
shpc_led_state_to_str(uint8_t value)127*86f0aa1dSVladimir Sementsov-Ogievskiy static const char *shpc_led_state_to_str(uint8_t value)
128*86f0aa1dSVladimir Sementsov-Ogievskiy {
129*86f0aa1dSVladimir Sementsov-Ogievskiy     switch (value) {
130*86f0aa1dSVladimir Sementsov-Ogievskiy     case SHPC_LED_ON:
131*86f0aa1dSVladimir Sementsov-Ogievskiy         return "on";
132*86f0aa1dSVladimir Sementsov-Ogievskiy     case SHPC_LED_BLINK:
133*86f0aa1dSVladimir Sementsov-Ogievskiy         return "blink";
134*86f0aa1dSVladimir Sementsov-Ogievskiy     case SHPC_LED_OFF:
135*86f0aa1dSVladimir Sementsov-Ogievskiy         return "off";
136*86f0aa1dSVladimir Sementsov-Ogievskiy     default:
137*86f0aa1dSVladimir Sementsov-Ogievskiy         return "invalid";
138*86f0aa1dSVladimir Sementsov-Ogievskiy     }
139*86f0aa1dSVladimir Sementsov-Ogievskiy }
140*86f0aa1dSVladimir Sementsov-Ogievskiy 
shpc_slot_state_to_str(uint8_t value)141*86f0aa1dSVladimir Sementsov-Ogievskiy static const char *shpc_slot_state_to_str(uint8_t value)
142*86f0aa1dSVladimir Sementsov-Ogievskiy {
143*86f0aa1dSVladimir Sementsov-Ogievskiy     switch (value) {
144*86f0aa1dSVladimir Sementsov-Ogievskiy     case SHPC_STATE_PWRONLY:
145*86f0aa1dSVladimir Sementsov-Ogievskiy         return "power-only";
146*86f0aa1dSVladimir Sementsov-Ogievskiy     case SHPC_STATE_ENABLED:
147*86f0aa1dSVladimir Sementsov-Ogievskiy         return "enabled";
148*86f0aa1dSVladimir Sementsov-Ogievskiy     case SHPC_STATE_DISABLED:
149*86f0aa1dSVladimir Sementsov-Ogievskiy         return "disabled";
150*86f0aa1dSVladimir Sementsov-Ogievskiy     default:
151*86f0aa1dSVladimir Sementsov-Ogievskiy         return "invalid";
152*86f0aa1dSVladimir Sementsov-Ogievskiy     }
153*86f0aa1dSVladimir Sementsov-Ogievskiy }
154*86f0aa1dSVladimir Sementsov-Ogievskiy 
shpc_get_status(SHPCDevice * shpc,int slot,uint16_t msk)15594c84780SVladimir Sementsov-Ogievskiy static uint8_t shpc_get_status(SHPCDevice *shpc, int slot, uint16_t msk)
156315a1350SMichael S. Tsirkin {
157315a1350SMichael S. Tsirkin     uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot);
15894c84780SVladimir Sementsov-Ogievskiy     uint16_t result = (pci_get_word(status) & msk) >> ctz32(msk);
15994c84780SVladimir Sementsov-Ogievskiy 
16094c84780SVladimir Sementsov-Ogievskiy     assert(result <= UINT8_MAX);
16194c84780SVladimir Sementsov-Ogievskiy     return result;
162315a1350SMichael S. Tsirkin }
163315a1350SMichael S. Tsirkin 
shpc_set_status(SHPCDevice * shpc,int slot,uint8_t value,uint16_t msk)164315a1350SMichael S. Tsirkin static void shpc_set_status(SHPCDevice *shpc,
165315a1350SMichael S. Tsirkin                             int slot, uint8_t value, uint16_t msk)
166315a1350SMichael S. Tsirkin {
167315a1350SMichael S. Tsirkin     uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot);
168315a1350SMichael S. Tsirkin     pci_word_test_and_clear_mask(status, msk);
169786a4ea8SStefan Hajnoczi     pci_word_test_and_set_mask(status, value << ctz32(msk));
170315a1350SMichael S. Tsirkin }
171315a1350SMichael S. Tsirkin 
shpc_interrupt_update(PCIDevice * d)172315a1350SMichael S. Tsirkin static void shpc_interrupt_update(PCIDevice *d)
173315a1350SMichael S. Tsirkin {
174315a1350SMichael S. Tsirkin     SHPCDevice *shpc = d->shpc;
175315a1350SMichael S. Tsirkin     int slot;
176315a1350SMichael S. Tsirkin     int level = 0;
177315a1350SMichael S. Tsirkin     uint32_t serr_int;
178315a1350SMichael S. Tsirkin     uint32_t int_locator = 0;
179315a1350SMichael S. Tsirkin 
180315a1350SMichael S. Tsirkin     /* Update interrupt locator register */
181315a1350SMichael S. Tsirkin     for (slot = 0; slot < shpc->nslots; ++slot) {
182315a1350SMichael S. Tsirkin         uint8_t event = shpc->config[SHPC_SLOT_EVENT_LATCH(slot)];
183315a1350SMichael S. Tsirkin         uint8_t disable = shpc->config[SHPC_SLOT_EVENT_SERR_INT_DIS(d, slot)];
18458209459SMichael S. Tsirkin         uint32_t mask = 1U << SHPC_IDX_TO_LOGICAL(slot);
185315a1350SMichael S. Tsirkin         if (event & ~disable) {
186315a1350SMichael S. Tsirkin             int_locator |= mask;
187315a1350SMichael S. Tsirkin         }
188315a1350SMichael S. Tsirkin     }
189315a1350SMichael S. Tsirkin     serr_int = pci_get_long(shpc->config + SHPC_SERR_INT);
190315a1350SMichael S. Tsirkin     if ((serr_int & SHPC_CMD_DETECTED) && !(serr_int & SHPC_CMD_INT_DIS)) {
191315a1350SMichael S. Tsirkin         int_locator |= SHPC_INT_COMMAND;
192315a1350SMichael S. Tsirkin     }
193315a1350SMichael S. Tsirkin     pci_set_long(shpc->config + SHPC_INT_LOCATOR, int_locator);
194315a1350SMichael S. Tsirkin     level = (!(serr_int & SHPC_INT_DIS) && int_locator) ? 1 : 0;
195315a1350SMichael S. Tsirkin     if (msi_enabled(d) && shpc->msi_requested != level)
196315a1350SMichael S. Tsirkin         msi_notify(d, 0);
197315a1350SMichael S. Tsirkin     else
1989e64f8a3SMarcel Apfelbaum         pci_set_irq(d, level);
199315a1350SMichael S. Tsirkin     shpc->msi_requested = level;
200315a1350SMichael S. Tsirkin }
201315a1350SMichael S. Tsirkin 
shpc_set_sec_bus_speed(SHPCDevice * shpc,uint8_t speed)202315a1350SMichael S. Tsirkin static void shpc_set_sec_bus_speed(SHPCDevice *shpc, uint8_t speed)
203315a1350SMichael S. Tsirkin {
204315a1350SMichael S. Tsirkin     switch (speed) {
205315a1350SMichael S. Tsirkin     case SHPC_SEC_BUS_33:
206315a1350SMichael S. Tsirkin         shpc->config[SHPC_SEC_BUS] &= ~SHPC_SEC_BUS_MASK;
207315a1350SMichael S. Tsirkin         shpc->config[SHPC_SEC_BUS] |= speed;
208315a1350SMichael S. Tsirkin         break;
209315a1350SMichael S. Tsirkin     default:
210315a1350SMichael S. Tsirkin         pci_word_test_and_set_mask(shpc->config + SHPC_CMD_STATUS,
211315a1350SMichael S. Tsirkin                                    SHPC_CMD_STATUS_INVALID_MODE);
212315a1350SMichael S. Tsirkin     }
213315a1350SMichael S. Tsirkin }
214315a1350SMichael S. Tsirkin 
shpc_reset(PCIDevice * d)215315a1350SMichael S. Tsirkin void shpc_reset(PCIDevice *d)
216315a1350SMichael S. Tsirkin {
217315a1350SMichael S. Tsirkin     SHPCDevice *shpc = d->shpc;
218315a1350SMichael S. Tsirkin     int nslots = shpc->nslots;
219315a1350SMichael S. Tsirkin     int i;
220315a1350SMichael S. Tsirkin     memset(shpc->config, 0, SHPC_SIZEOF(d));
221315a1350SMichael S. Tsirkin     pci_set_byte(shpc->config + SHPC_NSLOTS, nslots);
222315a1350SMichael S. Tsirkin     pci_set_long(shpc->config + SHPC_SLOTS_33, nslots);
223315a1350SMichael S. Tsirkin     pci_set_long(shpc->config + SHPC_SLOTS_66, 0);
224315a1350SMichael S. Tsirkin     pci_set_byte(shpc->config + SHPC_FIRST_DEV, SHPC_IDX_TO_PCI(0));
225315a1350SMichael S. Tsirkin     pci_set_word(shpc->config + SHPC_PHYS_SLOT,
226315a1350SMichael S. Tsirkin                  SHPC_IDX_TO_PHYSICAL(0) |
227315a1350SMichael S. Tsirkin                  SHPC_PHYS_NUM_UP |
228315a1350SMichael S. Tsirkin                  SHPC_PHYS_MRL |
229315a1350SMichael S. Tsirkin                  SHPC_PHYS_BUTTON);
230315a1350SMichael S. Tsirkin     pci_set_long(shpc->config + SHPC_SERR_INT, SHPC_INT_DIS |
231315a1350SMichael S. Tsirkin                  SHPC_SERR_DIS |
232315a1350SMichael S. Tsirkin                  SHPC_CMD_INT_DIS |
233315a1350SMichael S. Tsirkin                  SHPC_ARB_SERR_DIS);
234315a1350SMichael S. Tsirkin     pci_set_byte(shpc->config + SHPC_PROG_IFC, SHPC_PROG_IFC_1_0);
235315a1350SMichael S. Tsirkin     pci_set_word(shpc->config + SHPC_SEC_BUS, SHPC_SEC_BUS_33);
236315a1350SMichael S. Tsirkin     for (i = 0; i < shpc->nslots; ++i) {
237315a1350SMichael S. Tsirkin         pci_set_byte(shpc->config + SHPC_SLOT_EVENT_SERR_INT_DIS(d, i),
238315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_PRESENCE |
239315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_ISOLATED_FAULT |
240315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_BUTTON |
241315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_MRL |
242315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_CONNECTED_FAULT |
243315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_MRL_SERR_DIS |
244315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS);
245315a1350SMichael S. Tsirkin         if (shpc->sec_bus->devices[PCI_DEVFN(SHPC_IDX_TO_PCI(i), 0)]) {
246315a1350SMichael S. Tsirkin             shpc_set_status(shpc, i, SHPC_STATE_ENABLED, SHPC_SLOT_STATE_MASK);
247315a1350SMichael S. Tsirkin             shpc_set_status(shpc, i, 0, SHPC_SLOT_STATUS_MRL_OPEN);
248315a1350SMichael S. Tsirkin             shpc_set_status(shpc, i, SHPC_SLOT_STATUS_PRSNT_7_5W,
249315a1350SMichael S. Tsirkin                             SHPC_SLOT_STATUS_PRSNT_MASK);
250315a1350SMichael S. Tsirkin             shpc_set_status(shpc, i, SHPC_LED_ON, SHPC_SLOT_PWR_LED_MASK);
251315a1350SMichael S. Tsirkin         } else {
252315a1350SMichael S. Tsirkin             shpc_set_status(shpc, i, SHPC_STATE_DISABLED, SHPC_SLOT_STATE_MASK);
253315a1350SMichael S. Tsirkin             shpc_set_status(shpc, i, 1, SHPC_SLOT_STATUS_MRL_OPEN);
254315a1350SMichael S. Tsirkin             shpc_set_status(shpc, i, SHPC_SLOT_STATUS_PRSNT_EMPTY,
255315a1350SMichael S. Tsirkin                             SHPC_SLOT_STATUS_PRSNT_MASK);
256315a1350SMichael S. Tsirkin             shpc_set_status(shpc, i, SHPC_LED_OFF, SHPC_SLOT_PWR_LED_MASK);
257315a1350SMichael S. Tsirkin         }
25893af1274SVladimir Sementsov-Ogievskiy         shpc_set_status(shpc, i, SHPC_LED_OFF, SHPC_SLOT_ATTN_LED_MASK);
259315a1350SMichael S. Tsirkin         shpc_set_status(shpc, i, 0, SHPC_SLOT_STATUS_66);
260315a1350SMichael S. Tsirkin     }
261315a1350SMichael S. Tsirkin     shpc_set_sec_bus_speed(shpc, SHPC_SEC_BUS_33);
262315a1350SMichael S. Tsirkin     shpc->msi_requested = 0;
263315a1350SMichael S. Tsirkin     shpc_interrupt_update(d);
264315a1350SMichael S. Tsirkin }
265315a1350SMichael S. Tsirkin 
shpc_invalid_command(SHPCDevice * shpc)266315a1350SMichael S. Tsirkin static void shpc_invalid_command(SHPCDevice *shpc)
267315a1350SMichael S. Tsirkin {
268315a1350SMichael S. Tsirkin     pci_word_test_and_set_mask(shpc->config + SHPC_CMD_STATUS,
269315a1350SMichael S. Tsirkin                                SHPC_CMD_STATUS_INVALID_CMD);
270315a1350SMichael S. Tsirkin }
271315a1350SMichael S. Tsirkin 
shpc_free_devices_in_slot(SHPCDevice * shpc,int slot)272315a1350SMichael S. Tsirkin static void shpc_free_devices_in_slot(SHPCDevice *shpc, int slot)
273315a1350SMichael S. Tsirkin {
2748f560cdcSDavid Hildenbrand     HotplugHandler *hotplug_ctrl;
275315a1350SMichael S. Tsirkin     int devfn;
276315a1350SMichael S. Tsirkin     int pci_slot = SHPC_IDX_TO_PCI(slot);
277315a1350SMichael S. Tsirkin     for (devfn = PCI_DEVFN(pci_slot, 0);
278315a1350SMichael S. Tsirkin          devfn <= PCI_DEVFN(pci_slot, PCI_FUNC_MAX - 1);
279315a1350SMichael S. Tsirkin          ++devfn) {
280315a1350SMichael S. Tsirkin         PCIDevice *affected_dev = shpc->sec_bus->devices[devfn];
281315a1350SMichael S. Tsirkin         if (affected_dev) {
2828f560cdcSDavid Hildenbrand             hotplug_ctrl = qdev_get_hotplug_handler(DEVICE(affected_dev));
2838f560cdcSDavid Hildenbrand             hotplug_handler_unplug(hotplug_ctrl, DEVICE(affected_dev),
2848f560cdcSDavid Hildenbrand                                    &error_abort);
28507578b0aSDavid Hildenbrand             object_unparent(OBJECT(affected_dev));
286315a1350SMichael S. Tsirkin         }
287315a1350SMichael S. Tsirkin     }
288315a1350SMichael S. Tsirkin }
289315a1350SMichael S. Tsirkin 
shpc_slot_is_off(uint8_t state,uint8_t power,uint8_t attn)290dedf052aSVladimir Sementsov-Ogievskiy static bool shpc_slot_is_off(uint8_t state, uint8_t power, uint8_t attn)
291dedf052aSVladimir Sementsov-Ogievskiy {
292dedf052aSVladimir Sementsov-Ogievskiy     return state == SHPC_STATE_DISABLED && power == SHPC_LED_OFF;
293dedf052aSVladimir Sementsov-Ogievskiy }
294dedf052aSVladimir Sementsov-Ogievskiy 
shpc_slot_command(PCIDevice * d,uint8_t target,uint8_t state,uint8_t power,uint8_t attn)2950adc05f4SVladimir Sementsov-Ogievskiy static void shpc_slot_command(PCIDevice *d, uint8_t target,
296315a1350SMichael S. Tsirkin                               uint8_t state, uint8_t power, uint8_t attn)
297315a1350SMichael S. Tsirkin {
2980adc05f4SVladimir Sementsov-Ogievskiy     SHPCDevice *shpc = d->shpc;
299315a1350SMichael S. Tsirkin     int slot = SHPC_LOGICAL_TO_IDX(target);
300dedf052aSVladimir Sementsov-Ogievskiy     uint8_t old_state = shpc_get_status(shpc, slot, SHPC_SLOT_STATE_MASK);
301dedf052aSVladimir Sementsov-Ogievskiy     uint8_t old_power = shpc_get_status(shpc, slot, SHPC_SLOT_PWR_LED_MASK);
302dedf052aSVladimir Sementsov-Ogievskiy     uint8_t old_attn = shpc_get_status(shpc, slot, SHPC_SLOT_ATTN_LED_MASK);
303dedf052aSVladimir Sementsov-Ogievskiy 
304315a1350SMichael S. Tsirkin     if (target < SHPC_CMD_TRGT_MIN || slot >= shpc->nslots) {
305315a1350SMichael S. Tsirkin         shpc_invalid_command(shpc);
306315a1350SMichael S. Tsirkin         return;
307315a1350SMichael S. Tsirkin     }
308dedf052aSVladimir Sementsov-Ogievskiy 
309dedf052aSVladimir Sementsov-Ogievskiy     if (old_state == SHPC_STATE_ENABLED && state == SHPC_STATE_PWRONLY) {
310315a1350SMichael S. Tsirkin         shpc_invalid_command(shpc);
311315a1350SMichael S. Tsirkin         return;
312315a1350SMichael S. Tsirkin     }
313315a1350SMichael S. Tsirkin 
314dedf052aSVladimir Sementsov-Ogievskiy     if (power == SHPC_LED_NO) {
315dedf052aSVladimir Sementsov-Ogievskiy         power = old_power;
316dedf052aSVladimir Sementsov-Ogievskiy     } else {
317315a1350SMichael S. Tsirkin         /* TODO: send event to monitor */
318315a1350SMichael S. Tsirkin         shpc_set_status(shpc, slot, power, SHPC_SLOT_PWR_LED_MASK);
319315a1350SMichael S. Tsirkin     }
320dedf052aSVladimir Sementsov-Ogievskiy 
321dedf052aSVladimir Sementsov-Ogievskiy     if (attn == SHPC_LED_NO) {
322dedf052aSVladimir Sementsov-Ogievskiy         attn = old_attn;
323dedf052aSVladimir Sementsov-Ogievskiy     } else {
324315a1350SMichael S. Tsirkin         /* TODO: send event to monitor */
325315a1350SMichael S. Tsirkin         shpc_set_status(shpc, slot, attn, SHPC_SLOT_ATTN_LED_MASK);
326315a1350SMichael S. Tsirkin     }
327dedf052aSVladimir Sementsov-Ogievskiy 
328dedf052aSVladimir Sementsov-Ogievskiy     if (state == SHPC_STATE_NO) {
329dedf052aSVladimir Sementsov-Ogievskiy         state = old_state;
330dedf052aSVladimir Sementsov-Ogievskiy     } else {
331025e2088SVladimir Sementsov-Ogievskiy         shpc_set_status(shpc, slot, state, SHPC_SLOT_STATE_MASK);
332025e2088SVladimir Sementsov-Ogievskiy     }
333315a1350SMichael S. Tsirkin 
334*86f0aa1dSVladimir Sementsov-Ogievskiy     if (trace_event_get_state_backends(TRACE_SHPC_SLOT_COMMAND)) {
335*86f0aa1dSVladimir Sementsov-Ogievskiy         DeviceState *parent = DEVICE(d);
336*86f0aa1dSVladimir Sementsov-Ogievskiy         int pci_slot = SHPC_IDX_TO_PCI(slot);
337*86f0aa1dSVladimir Sementsov-Ogievskiy         DeviceState *child =
338*86f0aa1dSVladimir Sementsov-Ogievskiy             DEVICE(shpc->sec_bus->devices[PCI_DEVFN(pci_slot, 0)]);
339*86f0aa1dSVladimir Sementsov-Ogievskiy 
340*86f0aa1dSVladimir Sementsov-Ogievskiy         trace_shpc_slot_command(
341*86f0aa1dSVladimir Sementsov-Ogievskiy             parent->canonical_path, pci_slot,
342*86f0aa1dSVladimir Sementsov-Ogievskiy             child ? child->canonical_path : "no-child",
343*86f0aa1dSVladimir Sementsov-Ogievskiy             shpc_led_state_to_str(old_power),
344*86f0aa1dSVladimir Sementsov-Ogievskiy             shpc_led_state_to_str(power),
345*86f0aa1dSVladimir Sementsov-Ogievskiy             shpc_led_state_to_str(old_attn),
346*86f0aa1dSVladimir Sementsov-Ogievskiy             shpc_led_state_to_str(attn),
347*86f0aa1dSVladimir Sementsov-Ogievskiy             shpc_slot_state_to_str(old_state),
348*86f0aa1dSVladimir Sementsov-Ogievskiy             shpc_slot_state_to_str(state));
349*86f0aa1dSVladimir Sementsov-Ogievskiy     }
350*86f0aa1dSVladimir Sementsov-Ogievskiy 
351dedf052aSVladimir Sementsov-Ogievskiy     if (!shpc_slot_is_off(old_state, old_power, old_attn) &&
352dedf052aSVladimir Sementsov-Ogievskiy         shpc_slot_is_off(state, power, attn))
353025e2088SVladimir Sementsov-Ogievskiy     {
354315a1350SMichael S. Tsirkin         shpc_free_devices_in_slot(shpc, slot);
355315a1350SMichael S. Tsirkin         shpc_set_status(shpc, slot, 1, SHPC_SLOT_STATUS_MRL_OPEN);
356315a1350SMichael S. Tsirkin         shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_EMPTY,
357315a1350SMichael S. Tsirkin                         SHPC_SLOT_STATUS_PRSNT_MASK);
358315a1350SMichael S. Tsirkin         shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
359315a1350SMichael S. Tsirkin             SHPC_SLOT_EVENT_MRL |
360315a1350SMichael S. Tsirkin             SHPC_SLOT_EVENT_PRESENCE;
361315a1350SMichael S. Tsirkin     }
362315a1350SMichael S. Tsirkin }
363315a1350SMichael S. Tsirkin 
shpc_command(PCIDevice * d)3640adc05f4SVladimir Sementsov-Ogievskiy static void shpc_command(PCIDevice *d)
365315a1350SMichael S. Tsirkin {
3660adc05f4SVladimir Sementsov-Ogievskiy     SHPCDevice *shpc = d->shpc;
367315a1350SMichael S. Tsirkin     uint8_t code = pci_get_byte(shpc->config + SHPC_CMD_CODE);
368315a1350SMichael S. Tsirkin     uint8_t speed;
369315a1350SMichael S. Tsirkin     uint8_t target;
370315a1350SMichael S. Tsirkin     uint8_t attn;
371315a1350SMichael S. Tsirkin     uint8_t power;
372315a1350SMichael S. Tsirkin     uint8_t state;
373315a1350SMichael S. Tsirkin     int i;
374315a1350SMichael S. Tsirkin 
375315a1350SMichael S. Tsirkin     /* Clear status from the previous command. */
376315a1350SMichael S. Tsirkin     pci_word_test_and_clear_mask(shpc->config + SHPC_CMD_STATUS,
377315a1350SMichael S. Tsirkin                                  SHPC_CMD_STATUS_BUSY |
378315a1350SMichael S. Tsirkin                                  SHPC_CMD_STATUS_MRL_OPEN |
379315a1350SMichael S. Tsirkin                                  SHPC_CMD_STATUS_INVALID_CMD |
380315a1350SMichael S. Tsirkin                                  SHPC_CMD_STATUS_INVALID_MODE);
381315a1350SMichael S. Tsirkin     switch (code) {
382315a1350SMichael S. Tsirkin     case 0x00 ... 0x3f:
383315a1350SMichael S. Tsirkin         target = shpc->config[SHPC_CMD_TRGT] & SHPC_CMD_TRGT_MAX;
384315a1350SMichael S. Tsirkin         state = (code & SHPC_SLOT_STATE_MASK) >> SHPC_SLOT_STATE_SHIFT;
385315a1350SMichael S. Tsirkin         power = (code & SHPC_SLOT_PWR_LED_MASK) >> SHPC_SLOT_PWR_LED_SHIFT;
386315a1350SMichael S. Tsirkin         attn = (code & SHPC_SLOT_ATTN_LED_MASK) >> SHPC_SLOT_ATTN_LED_SHIFT;
3870adc05f4SVladimir Sementsov-Ogievskiy         shpc_slot_command(d, target, state, power, attn);
388315a1350SMichael S. Tsirkin         break;
389315a1350SMichael S. Tsirkin     case 0x40 ... 0x47:
390315a1350SMichael S. Tsirkin         speed = code & SHPC_SEC_BUS_MASK;
391315a1350SMichael S. Tsirkin         shpc_set_sec_bus_speed(shpc, speed);
392315a1350SMichael S. Tsirkin         break;
393315a1350SMichael S. Tsirkin     case 0x48:
394315a1350SMichael S. Tsirkin         /* Power only all slots */
395315a1350SMichael S. Tsirkin         /* first verify no slots are enabled */
396315a1350SMichael S. Tsirkin         for (i = 0; i < shpc->nslots; ++i) {
397315a1350SMichael S. Tsirkin             state = shpc_get_status(shpc, i, SHPC_SLOT_STATE_MASK);
398315a1350SMichael S. Tsirkin             if (state == SHPC_STATE_ENABLED) {
399315a1350SMichael S. Tsirkin                 shpc_invalid_command(shpc);
400315a1350SMichael S. Tsirkin                 goto done;
401315a1350SMichael S. Tsirkin             }
402315a1350SMichael S. Tsirkin         }
403315a1350SMichael S. Tsirkin         for (i = 0; i < shpc->nslots; ++i) {
404315a1350SMichael S. Tsirkin             if (!(shpc_get_status(shpc, i, SHPC_SLOT_STATUS_MRL_OPEN))) {
4050adc05f4SVladimir Sementsov-Ogievskiy                 shpc_slot_command(d, i + SHPC_CMD_TRGT_MIN,
406315a1350SMichael S. Tsirkin                                   SHPC_STATE_PWRONLY, SHPC_LED_ON, SHPC_LED_NO);
407315a1350SMichael S. Tsirkin             } else {
4080adc05f4SVladimir Sementsov-Ogievskiy                 shpc_slot_command(d, i + SHPC_CMD_TRGT_MIN,
409315a1350SMichael S. Tsirkin                                   SHPC_STATE_NO, SHPC_LED_OFF, SHPC_LED_NO);
410315a1350SMichael S. Tsirkin             }
411315a1350SMichael S. Tsirkin         }
412315a1350SMichael S. Tsirkin         break;
413315a1350SMichael S. Tsirkin     case 0x49:
414315a1350SMichael S. Tsirkin         /* Enable all slots */
415315a1350SMichael S. Tsirkin         /* TODO: Spec says this shall fail if some are already enabled.
416315a1350SMichael S. Tsirkin          * This doesn't make sense - why not? a spec bug? */
417315a1350SMichael S. Tsirkin         for (i = 0; i < shpc->nslots; ++i) {
418315a1350SMichael S. Tsirkin             state = shpc_get_status(shpc, i, SHPC_SLOT_STATE_MASK);
419315a1350SMichael S. Tsirkin             if (state == SHPC_STATE_ENABLED) {
420315a1350SMichael S. Tsirkin                 shpc_invalid_command(shpc);
421315a1350SMichael S. Tsirkin                 goto done;
422315a1350SMichael S. Tsirkin             }
423315a1350SMichael S. Tsirkin         }
424315a1350SMichael S. Tsirkin         for (i = 0; i < shpc->nslots; ++i) {
425315a1350SMichael S. Tsirkin             if (!(shpc_get_status(shpc, i, SHPC_SLOT_STATUS_MRL_OPEN))) {
4260adc05f4SVladimir Sementsov-Ogievskiy                 shpc_slot_command(d, i + SHPC_CMD_TRGT_MIN,
427315a1350SMichael S. Tsirkin                                   SHPC_STATE_ENABLED, SHPC_LED_ON, SHPC_LED_NO);
428315a1350SMichael S. Tsirkin             } else {
4290adc05f4SVladimir Sementsov-Ogievskiy                 shpc_slot_command(d, i + SHPC_CMD_TRGT_MIN,
430315a1350SMichael S. Tsirkin                                   SHPC_STATE_NO, SHPC_LED_OFF, SHPC_LED_NO);
431315a1350SMichael S. Tsirkin             }
432315a1350SMichael S. Tsirkin         }
433315a1350SMichael S. Tsirkin         break;
434315a1350SMichael S. Tsirkin     default:
435315a1350SMichael S. Tsirkin         shpc_invalid_command(shpc);
436315a1350SMichael S. Tsirkin         break;
437315a1350SMichael S. Tsirkin     }
438315a1350SMichael S. Tsirkin done:
439315a1350SMichael S. Tsirkin     pci_long_test_and_set_mask(shpc->config + SHPC_SERR_INT, SHPC_CMD_DETECTED);
440315a1350SMichael S. Tsirkin }
441315a1350SMichael S. Tsirkin 
shpc_write(PCIDevice * d,unsigned addr,uint64_t val,int l)442315a1350SMichael S. Tsirkin static void shpc_write(PCIDevice *d, unsigned addr, uint64_t val, int l)
443315a1350SMichael S. Tsirkin {
444315a1350SMichael S. Tsirkin     SHPCDevice *shpc = d->shpc;
445315a1350SMichael S. Tsirkin     int i;
446315a1350SMichael S. Tsirkin     if (addr >= SHPC_SIZEOF(d)) {
447315a1350SMichael S. Tsirkin         return;
448315a1350SMichael S. Tsirkin     }
449315a1350SMichael S. Tsirkin     l = MIN(l, SHPC_SIZEOF(d) - addr);
450315a1350SMichael S. Tsirkin 
451315a1350SMichael S. Tsirkin     /* TODO: code duplicated from pci.c */
452315a1350SMichael S. Tsirkin     for (i = 0; i < l; val >>= 8, ++i) {
453315a1350SMichael S. Tsirkin         unsigned a = addr + i;
454315a1350SMichael S. Tsirkin         uint8_t wmask = shpc->wmask[a];
455315a1350SMichael S. Tsirkin         uint8_t w1cmask = shpc->w1cmask[a];
456315a1350SMichael S. Tsirkin         assert(!(wmask & w1cmask));
457315a1350SMichael S. Tsirkin         shpc->config[a] = (shpc->config[a] & ~wmask) | (val & wmask);
458315a1350SMichael S. Tsirkin         shpc->config[a] &= ~(val & w1cmask); /* W1C: Write 1 to Clear */
459315a1350SMichael S. Tsirkin     }
460315a1350SMichael S. Tsirkin     if (ranges_overlap(addr, l, SHPC_CMD_CODE, 2)) {
4610adc05f4SVladimir Sementsov-Ogievskiy         shpc_command(d);
462315a1350SMichael S. Tsirkin     }
463315a1350SMichael S. Tsirkin     shpc_interrupt_update(d);
464315a1350SMichael S. Tsirkin }
465315a1350SMichael S. Tsirkin 
shpc_read(PCIDevice * d,unsigned addr,int l)466315a1350SMichael S. Tsirkin static uint64_t shpc_read(PCIDevice *d, unsigned addr, int l)
467315a1350SMichael S. Tsirkin {
468315a1350SMichael S. Tsirkin     uint64_t val = 0x0;
469315a1350SMichael S. Tsirkin     if (addr >= SHPC_SIZEOF(d)) {
470315a1350SMichael S. Tsirkin         return val;
471315a1350SMichael S. Tsirkin     }
472315a1350SMichael S. Tsirkin     l = MIN(l, SHPC_SIZEOF(d) - addr);
473315a1350SMichael S. Tsirkin     memcpy(&val, d->shpc->config + addr, l);
474315a1350SMichael S. Tsirkin     return val;
475315a1350SMichael S. Tsirkin }
476315a1350SMichael S. Tsirkin 
477315a1350SMichael S. Tsirkin /* SHPC Bridge Capability */
478315a1350SMichael S. Tsirkin #define SHPC_CAP_LENGTH 0x08
479315a1350SMichael S. Tsirkin #define SHPC_CAP_DWORD_SELECT 0x2 /* 1 byte */
480315a1350SMichael S. Tsirkin #define SHPC_CAP_CxP 0x3 /* 1 byte: CSP, CIP */
481315a1350SMichael S. Tsirkin #define SHPC_CAP_DWORD_DATA 0x4 /* 4 bytes */
482315a1350SMichael S. Tsirkin #define SHPC_CAP_CSP_MASK 0x4
483315a1350SMichael S. Tsirkin #define SHPC_CAP_CIP_MASK 0x8
484315a1350SMichael S. Tsirkin 
shpc_cap_dword(PCIDevice * d)485315a1350SMichael S. Tsirkin static uint8_t shpc_cap_dword(PCIDevice *d)
486315a1350SMichael S. Tsirkin {
487315a1350SMichael S. Tsirkin     return pci_get_byte(d->config + d->shpc->cap + SHPC_CAP_DWORD_SELECT);
488315a1350SMichael S. Tsirkin }
489315a1350SMichael S. Tsirkin 
490315a1350SMichael S. Tsirkin /* Update dword data capability register */
shpc_cap_update_dword(PCIDevice * d)491315a1350SMichael S. Tsirkin static void shpc_cap_update_dword(PCIDevice *d)
492315a1350SMichael S. Tsirkin {
493315a1350SMichael S. Tsirkin     unsigned data;
494315a1350SMichael S. Tsirkin     data = shpc_read(d, shpc_cap_dword(d) * 4, 4);
495315a1350SMichael S. Tsirkin     pci_set_long(d->config  + d->shpc->cap + SHPC_CAP_DWORD_DATA, data);
496315a1350SMichael S. Tsirkin }
497315a1350SMichael S. Tsirkin 
498315a1350SMichael S. Tsirkin /* Add SHPC capability to the config space for the device. */
shpc_cap_add_config(PCIDevice * d,Error ** errp)499344475e7SMao Zhongyi static int shpc_cap_add_config(PCIDevice *d, Error **errp)
500315a1350SMichael S. Tsirkin {
501315a1350SMichael S. Tsirkin     uint8_t *config;
502315a1350SMichael S. Tsirkin     int config_offset;
503315a1350SMichael S. Tsirkin     config_offset = pci_add_capability(d, PCI_CAP_ID_SHPC,
5049a7c2a59SMao Zhongyi                                        0, SHPC_CAP_LENGTH,
505344475e7SMao Zhongyi                                        errp);
506315a1350SMichael S. Tsirkin     if (config_offset < 0) {
507315a1350SMichael S. Tsirkin         return config_offset;
508315a1350SMichael S. Tsirkin     }
509315a1350SMichael S. Tsirkin     config = d->config + config_offset;
510315a1350SMichael S. Tsirkin 
511315a1350SMichael S. Tsirkin     pci_set_byte(config + SHPC_CAP_DWORD_SELECT, 0);
512315a1350SMichael S. Tsirkin     pci_set_byte(config + SHPC_CAP_CxP, 0);
513315a1350SMichael S. Tsirkin     pci_set_long(config + SHPC_CAP_DWORD_DATA, 0);
514315a1350SMichael S. Tsirkin     d->shpc->cap = config_offset;
5159323e79fSPeter Maydell     /* Make dword select and data writable. */
516315a1350SMichael S. Tsirkin     pci_set_byte(d->wmask + config_offset + SHPC_CAP_DWORD_SELECT, 0xff);
517315a1350SMichael S. Tsirkin     pci_set_long(d->wmask + config_offset + SHPC_CAP_DWORD_DATA, 0xffffffff);
518315a1350SMichael S. Tsirkin     return 0;
519315a1350SMichael S. Tsirkin }
520315a1350SMichael S. Tsirkin 
shpc_mmio_read(void * opaque,hwaddr addr,unsigned size)521315a1350SMichael S. Tsirkin static uint64_t shpc_mmio_read(void *opaque, hwaddr addr,
522315a1350SMichael S. Tsirkin                                unsigned size)
523315a1350SMichael S. Tsirkin {
524315a1350SMichael S. Tsirkin     return shpc_read(opaque, addr, size);
525315a1350SMichael S. Tsirkin }
526315a1350SMichael S. Tsirkin 
shpc_mmio_write(void * opaque,hwaddr addr,uint64_t val,unsigned size)527315a1350SMichael S. Tsirkin static void shpc_mmio_write(void *opaque, hwaddr addr,
528315a1350SMichael S. Tsirkin                             uint64_t val, unsigned size)
529315a1350SMichael S. Tsirkin {
530315a1350SMichael S. Tsirkin     shpc_write(opaque, addr, val, size);
531315a1350SMichael S. Tsirkin }
532315a1350SMichael S. Tsirkin 
533315a1350SMichael S. Tsirkin static const MemoryRegionOps shpc_mmio_ops = {
534315a1350SMichael S. Tsirkin     .read = shpc_mmio_read,
535315a1350SMichael S. Tsirkin     .write = shpc_mmio_write,
536315a1350SMichael S. Tsirkin     .endianness = DEVICE_LITTLE_ENDIAN,
537315a1350SMichael S. Tsirkin     .valid = {
538315a1350SMichael S. Tsirkin         /* SHPC ECN requires dword accesses, but the original 1.0 spec doesn't.
539118d4ed0SDr. David Alan Gilbert          * It's easier to support all sizes than worry about it.
540118d4ed0SDr. David Alan Gilbert          */
541315a1350SMichael S. Tsirkin         .min_access_size = 1,
542315a1350SMichael S. Tsirkin         .max_access_size = 4,
543315a1350SMichael S. Tsirkin     },
544315a1350SMichael S. Tsirkin };
54505d8a107SVladimir Sementsov-Ogievskiy 
shpc_device_get_slot(PCIDevice * affected_dev,int * slot,SHPCDevice * shpc,Error ** errp)54605d8a107SVladimir Sementsov-Ogievskiy static bool shpc_device_get_slot(PCIDevice *affected_dev, int *slot,
5475d268704SIgor Mammedov                                  SHPCDevice *shpc, Error **errp)
548315a1350SMichael S. Tsirkin {
549315a1350SMichael S. Tsirkin     int pci_slot = PCI_SLOT(affected_dev->devfn);
5505d268704SIgor Mammedov     *slot = SHPC_PCI_TO_IDX(pci_slot);
5515d268704SIgor Mammedov 
5525d268704SIgor Mammedov     if (pci_slot < SHPC_IDX_TO_PCI(0) || *slot >= shpc->nslots) {
5535d268704SIgor Mammedov         error_setg(errp, "Unsupported PCI slot %d for standard hotplug "
554315a1350SMichael S. Tsirkin                    "controller. Valid slots are between %d and %d.",
555315a1350SMichael S. Tsirkin                    pci_slot, SHPC_IDX_TO_PCI(0),
556315a1350SMichael S. Tsirkin                    SHPC_IDX_TO_PCI(shpc->nslots) - 1);
55705d8a107SVladimir Sementsov-Ogievskiy         return false;
558315a1350SMichael S. Tsirkin     }
55905d8a107SVladimir Sementsov-Ogievskiy 
56005d8a107SVladimir Sementsov-Ogievskiy     return true;
5615d268704SIgor Mammedov }
5625d268704SIgor Mammedov 
shpc_device_plug_cb(HotplugHandler * hotplug_dev,DeviceState * dev,Error ** errp)563851fedfbSDavid Hildenbrand void shpc_device_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
5645d268704SIgor Mammedov                             Error **errp)
5655d268704SIgor Mammedov {
5665d268704SIgor Mammedov     PCIDevice *pci_hotplug_dev = PCI_DEVICE(hotplug_dev);
5675d268704SIgor Mammedov     SHPCDevice *shpc = pci_hotplug_dev->shpc;
5685d268704SIgor Mammedov     int slot;
5695d268704SIgor Mammedov 
57005d8a107SVladimir Sementsov-Ogievskiy     if (!shpc_device_get_slot(PCI_DEVICE(dev), &slot, shpc, errp)) {
5715d268704SIgor Mammedov         return;
5725d268704SIgor Mammedov     }
5735d268704SIgor Mammedov 
574315a1350SMichael S. Tsirkin     /* Don't send event when device is enabled during qemu machine creation:
575315a1350SMichael S. Tsirkin      * it is present on boot, no hotplug event is necessary. We do send an
576315a1350SMichael S. Tsirkin      * event when the device is disabled later. */
5775d268704SIgor Mammedov     if (!dev->hotplugged) {
578315a1350SMichael S. Tsirkin         shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_MRL_OPEN);
579315a1350SMichael S. Tsirkin         shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_7_5W,
580315a1350SMichael S. Tsirkin                         SHPC_SLOT_STATUS_PRSNT_MASK);
5815d268704SIgor Mammedov         return;
582315a1350SMichael S. Tsirkin     }
5835d268704SIgor Mammedov 
584315a1350SMichael S. Tsirkin     /* This could be a cancellation of the previous removal.
585315a1350SMichael S. Tsirkin      * We check MRL state to figure out. */
586315a1350SMichael S. Tsirkin     if (shpc_get_status(shpc, slot, SHPC_SLOT_STATUS_MRL_OPEN)) {
587315a1350SMichael S. Tsirkin         shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_MRL_OPEN);
588315a1350SMichael S. Tsirkin         shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_7_5W,
589315a1350SMichael S. Tsirkin                         SHPC_SLOT_STATUS_PRSNT_MASK);
590315a1350SMichael S. Tsirkin         shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
591315a1350SMichael S. Tsirkin             SHPC_SLOT_EVENT_BUTTON |
592315a1350SMichael S. Tsirkin             SHPC_SLOT_EVENT_MRL |
593315a1350SMichael S. Tsirkin             SHPC_SLOT_EVENT_PRESENCE;
594315a1350SMichael S. Tsirkin     } else {
595315a1350SMichael S. Tsirkin         /* Press attention button to cancel removal */
596315a1350SMichael S. Tsirkin         shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
597315a1350SMichael S. Tsirkin             SHPC_SLOT_EVENT_BUTTON;
598315a1350SMichael S. Tsirkin     }
5995d268704SIgor Mammedov     shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_66);
6005d268704SIgor Mammedov     shpc_interrupt_update(pci_hotplug_dev);
6015d268704SIgor Mammedov }
6025d268704SIgor Mammedov 
shpc_device_unplug_cb(HotplugHandler * hotplug_dev,DeviceState * dev,Error ** errp)6038f560cdcSDavid Hildenbrand void shpc_device_unplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
6048f560cdcSDavid Hildenbrand                            Error **errp)
6058f560cdcSDavid Hildenbrand {
606981c3dcdSMarkus Armbruster     qdev_unrealize(dev);
6078f560cdcSDavid Hildenbrand }
6088f560cdcSDavid Hildenbrand 
shpc_device_unplug_request_cb(HotplugHandler * hotplug_dev,DeviceState * dev,Error ** errp)609851fedfbSDavid Hildenbrand void shpc_device_unplug_request_cb(HotplugHandler *hotplug_dev,
61014d5a28fSIgor Mammedov                                    DeviceState *dev, Error **errp)
6115d268704SIgor Mammedov {
6125d268704SIgor Mammedov     PCIDevice *pci_hotplug_dev = PCI_DEVICE(hotplug_dev);
6135d268704SIgor Mammedov     SHPCDevice *shpc = pci_hotplug_dev->shpc;
6145d268704SIgor Mammedov     uint8_t state;
6155d268704SIgor Mammedov     uint8_t led;
6165d268704SIgor Mammedov     int slot;
6175d268704SIgor Mammedov 
61805d8a107SVladimir Sementsov-Ogievskiy     if (!shpc_device_get_slot(PCI_DEVICE(dev), &slot, shpc, errp)) {
6195d268704SIgor Mammedov         return;
6205d268704SIgor Mammedov     }
6215d268704SIgor Mammedov 
6225d268704SIgor Mammedov     state = shpc_get_status(shpc, slot, SHPC_SLOT_STATE_MASK);
6235d268704SIgor Mammedov     led = shpc_get_status(shpc, slot, SHPC_SLOT_PWR_LED_MASK);
6249ce75d4dSVladimir Sementsov-Ogievskiy 
6259ce75d4dSVladimir Sementsov-Ogievskiy     if (led == SHPC_LED_BLINK) {
6269ce75d4dSVladimir Sementsov-Ogievskiy         error_setg(errp, "Hot-unplug failed: "
6279ce75d4dSVladimir Sementsov-Ogievskiy                    "guest is busy (power indicator blinking)");
6289ce75d4dSVladimir Sementsov-Ogievskiy         return;
6299ce75d4dSVladimir Sementsov-Ogievskiy     }
6309ce75d4dSVladimir Sementsov-Ogievskiy 
6315d268704SIgor Mammedov     if (state == SHPC_STATE_DISABLED && led == SHPC_LED_OFF) {
6325d268704SIgor Mammedov         shpc_free_devices_in_slot(shpc, slot);
6335d268704SIgor Mammedov         shpc_set_status(shpc, slot, 1, SHPC_SLOT_STATUS_MRL_OPEN);
6345d268704SIgor Mammedov         shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_EMPTY,
6355d268704SIgor Mammedov                         SHPC_SLOT_STATUS_PRSNT_MASK);
6365d268704SIgor Mammedov         shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
6375d268704SIgor Mammedov             SHPC_SLOT_EVENT_MRL |
6385d268704SIgor Mammedov             SHPC_SLOT_EVENT_PRESENCE;
6395d593bdfSRoman Kagan     } else {
6405d593bdfSRoman Kagan         shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |= SHPC_SLOT_EVENT_BUTTON;
641315a1350SMichael S. Tsirkin     }
642315a1350SMichael S. Tsirkin     shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_66);
6435d268704SIgor Mammedov     shpc_interrupt_update(pci_hotplug_dev);
644315a1350SMichael S. Tsirkin }
645315a1350SMichael S. Tsirkin 
646315a1350SMichael S. Tsirkin /* Initialize the SHPC structure in bridge's BAR. */
shpc_init(PCIDevice * d,PCIBus * sec_bus,MemoryRegion * bar,unsigned offset,Error ** errp)647344475e7SMao Zhongyi int shpc_init(PCIDevice *d, PCIBus *sec_bus, MemoryRegion *bar,
648344475e7SMao Zhongyi               unsigned offset, Error **errp)
649315a1350SMichael S. Tsirkin {
650315a1350SMichael S. Tsirkin     int i, ret;
651315a1350SMichael S. Tsirkin     int nslots = SHPC_MAX_SLOTS; /* TODO: qdev property? */
652315a1350SMichael S. Tsirkin     SHPCDevice *shpc = d->shpc = g_malloc0(sizeof(*d->shpc));
653315a1350SMichael S. Tsirkin     shpc->sec_bus = sec_bus;
654344475e7SMao Zhongyi     ret = shpc_cap_add_config(d, errp);
655315a1350SMichael S. Tsirkin     if (ret) {
656315a1350SMichael S. Tsirkin         g_free(d->shpc);
657315a1350SMichael S. Tsirkin         return ret;
658315a1350SMichael S. Tsirkin     }
659315a1350SMichael S. Tsirkin     if (nslots < SHPC_MIN_SLOTS) {
660315a1350SMichael S. Tsirkin         return 0;
661315a1350SMichael S. Tsirkin     }
662315a1350SMichael S. Tsirkin     if (nslots > SHPC_MAX_SLOTS ||
663315a1350SMichael S. Tsirkin         SHPC_IDX_TO_PCI(nslots) > PCI_SLOT_MAX) {
664f1c0cff8SMichael Tokarev         /* TODO: report an error message that makes sense. */
665315a1350SMichael S. Tsirkin         return -EINVAL;
666315a1350SMichael S. Tsirkin     }
667315a1350SMichael S. Tsirkin     shpc->nslots = nslots;
668315a1350SMichael S. Tsirkin     shpc->config = g_malloc0(SHPC_SIZEOF(d));
669315a1350SMichael S. Tsirkin     shpc->cmask = g_malloc0(SHPC_SIZEOF(d));
670315a1350SMichael S. Tsirkin     shpc->wmask = g_malloc0(SHPC_SIZEOF(d));
671315a1350SMichael S. Tsirkin     shpc->w1cmask = g_malloc0(SHPC_SIZEOF(d));
672315a1350SMichael S. Tsirkin 
673315a1350SMichael S. Tsirkin     shpc_reset(d);
674315a1350SMichael S. Tsirkin 
675315a1350SMichael S. Tsirkin     pci_set_long(shpc->config + SHPC_BASE_OFFSET, offset);
676315a1350SMichael S. Tsirkin 
677315a1350SMichael S. Tsirkin     pci_set_byte(shpc->wmask + SHPC_CMD_CODE, 0xff);
678315a1350SMichael S. Tsirkin     pci_set_byte(shpc->wmask + SHPC_CMD_TRGT, SHPC_CMD_TRGT_MAX);
679315a1350SMichael S. Tsirkin     pci_set_byte(shpc->wmask + SHPC_CMD_TRGT, SHPC_CMD_TRGT_MAX);
680315a1350SMichael S. Tsirkin     pci_set_long(shpc->wmask + SHPC_SERR_INT,
681315a1350SMichael S. Tsirkin                  SHPC_INT_DIS |
682315a1350SMichael S. Tsirkin                  SHPC_SERR_DIS |
683315a1350SMichael S. Tsirkin                  SHPC_CMD_INT_DIS |
684315a1350SMichael S. Tsirkin                  SHPC_ARB_SERR_DIS);
685315a1350SMichael S. Tsirkin     pci_set_long(shpc->w1cmask + SHPC_SERR_INT,
686315a1350SMichael S. Tsirkin                  SHPC_CMD_DETECTED |
687315a1350SMichael S. Tsirkin                  SHPC_ARB_DETECTED);
688315a1350SMichael S. Tsirkin     for (i = 0; i < nslots; ++i) {
689315a1350SMichael S. Tsirkin         pci_set_byte(shpc->wmask +
690315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_SERR_INT_DIS(d, i),
691315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_PRESENCE |
692315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_ISOLATED_FAULT |
693315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_BUTTON |
694315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_MRL |
695315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_CONNECTED_FAULT |
696315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_MRL_SERR_DIS |
697315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS);
698315a1350SMichael S. Tsirkin         pci_set_byte(shpc->w1cmask +
699315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_LATCH(i),
700315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_PRESENCE |
701315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_ISOLATED_FAULT |
702315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_BUTTON |
703315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_MRL |
704315a1350SMichael S. Tsirkin                      SHPC_SLOT_EVENT_CONNECTED_FAULT);
705315a1350SMichael S. Tsirkin     }
706315a1350SMichael S. Tsirkin 
707315a1350SMichael S. Tsirkin     /* TODO: init cmask */
70840c5dce9SPaolo Bonzini     memory_region_init_io(&shpc->mmio, OBJECT(d), &shpc_mmio_ops,
70940c5dce9SPaolo Bonzini                           d, "shpc-mmio", SHPC_SIZEOF(d));
710315a1350SMichael S. Tsirkin     shpc_cap_update_dword(d);
711315a1350SMichael S. Tsirkin     memory_region_add_subregion(bar, offset, &shpc->mmio);
7125d268704SIgor Mammedov 
7139bc6bfdfSMarkus Armbruster     qbus_set_hotplug_handler(BUS(sec_bus), OBJECT(d));
714315a1350SMichael S. Tsirkin 
715315a1350SMichael S. Tsirkin     d->cap_present |= QEMU_PCI_CAP_SHPC;
716315a1350SMichael S. Tsirkin     return 0;
717315a1350SMichael S. Tsirkin }
718315a1350SMichael S. Tsirkin 
shpc_bar_size(PCIDevice * d)719315a1350SMichael S. Tsirkin int shpc_bar_size(PCIDevice *d)
720315a1350SMichael S. Tsirkin {
72137e626ceSYuval Shaia     return pow2roundup32(SHPC_SLOT_REG(SHPC_MAX_SLOTS));
722315a1350SMichael S. Tsirkin }
723315a1350SMichael S. Tsirkin 
shpc_cleanup(PCIDevice * d,MemoryRegion * bar)724315a1350SMichael S. Tsirkin void shpc_cleanup(PCIDevice *d, MemoryRegion *bar)
725315a1350SMichael S. Tsirkin {
726315a1350SMichael S. Tsirkin     SHPCDevice *shpc = d->shpc;
727315a1350SMichael S. Tsirkin     d->cap_present &= ~QEMU_PCI_CAP_SHPC;
728315a1350SMichael S. Tsirkin     memory_region_del_subregion(bar, &shpc->mmio);
729315a1350SMichael S. Tsirkin     /* TODO: cleanup config space changes? */
7305cd5e701SPaolo Bonzini }
7315cd5e701SPaolo Bonzini 
shpc_free(PCIDevice * d)7325cd5e701SPaolo Bonzini void shpc_free(PCIDevice *d)
7335cd5e701SPaolo Bonzini {
7345cd5e701SPaolo Bonzini     SHPCDevice *shpc = d->shpc;
7355cd5e701SPaolo Bonzini     if (!shpc) {
7365cd5e701SPaolo Bonzini         return;
7375cd5e701SPaolo Bonzini     }
7385cd5e701SPaolo Bonzini     object_unparent(OBJECT(&shpc->mmio));
739315a1350SMichael S. Tsirkin     g_free(shpc->config);
740315a1350SMichael S. Tsirkin     g_free(shpc->cmask);
741315a1350SMichael S. Tsirkin     g_free(shpc->wmask);
742315a1350SMichael S. Tsirkin     g_free(shpc->w1cmask);
743315a1350SMichael S. Tsirkin     g_free(shpc);
7445cd5e701SPaolo Bonzini     d->shpc = NULL;
745315a1350SMichael S. Tsirkin }
746315a1350SMichael S. Tsirkin 
shpc_cap_write_config(PCIDevice * d,uint32_t addr,uint32_t val,int l)747315a1350SMichael S. Tsirkin void shpc_cap_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int l)
748315a1350SMichael S. Tsirkin {
749315a1350SMichael S. Tsirkin     if (!ranges_overlap(addr, l, d->shpc->cap, SHPC_CAP_LENGTH)) {
750315a1350SMichael S. Tsirkin         return;
751315a1350SMichael S. Tsirkin     }
752315a1350SMichael S. Tsirkin     if (ranges_overlap(addr, l, d->shpc->cap + SHPC_CAP_DWORD_DATA, 4)) {
753315a1350SMichael S. Tsirkin         unsigned dword_data;
754315a1350SMichael S. Tsirkin         dword_data = pci_get_long(d->shpc->config + d->shpc->cap
755315a1350SMichael S. Tsirkin                                   + SHPC_CAP_DWORD_DATA);
756315a1350SMichael S. Tsirkin         shpc_write(d, shpc_cap_dword(d) * 4, dword_data, 4);
757315a1350SMichael S. Tsirkin     }
758315a1350SMichael S. Tsirkin     /* Update cap dword data in case guest is going to read it. */
759315a1350SMichael S. Tsirkin     shpc_cap_update_dword(d);
760315a1350SMichael S. Tsirkin }
761315a1350SMichael S. Tsirkin 
shpc_save(QEMUFile * f,void * pv,size_t size,const VMStateField * field,JSONWriter * vmdesc)76203fee66fSMarc-André Lureau static int shpc_save(QEMUFile *f, void *pv, size_t size,
7633ddba9a9SMarkus Armbruster                      const VMStateField *field, JSONWriter *vmdesc)
764315a1350SMichael S. Tsirkin {
765315a1350SMichael S. Tsirkin     PCIDevice *d = container_of(pv, PCIDevice, shpc);
766315a1350SMichael S. Tsirkin     qemu_put_buffer(f, d->shpc->config, SHPC_SIZEOF(d));
7672c21ee76SJianjun Duan 
7682c21ee76SJianjun Duan     return 0;
769315a1350SMichael S. Tsirkin }
770315a1350SMichael S. Tsirkin 
shpc_load(QEMUFile * f,void * pv,size_t size,const VMStateField * field)77103fee66fSMarc-André Lureau static int shpc_load(QEMUFile *f, void *pv, size_t size,
77203fee66fSMarc-André Lureau                      const VMStateField *field)
773315a1350SMichael S. Tsirkin {
774315a1350SMichael S. Tsirkin     PCIDevice *d = container_of(pv, PCIDevice, shpc);
775315a1350SMichael S. Tsirkin     int ret = qemu_get_buffer(f, d->shpc->config, SHPC_SIZEOF(d));
776315a1350SMichael S. Tsirkin     if (ret != SHPC_SIZEOF(d)) {
777315a1350SMichael S. Tsirkin         return -EINVAL;
778315a1350SMichael S. Tsirkin     }
779315a1350SMichael S. Tsirkin     /* Make sure we don't lose notifications. An extra interrupt is harmless. */
780315a1350SMichael S. Tsirkin     d->shpc->msi_requested = 0;
781315a1350SMichael S. Tsirkin     shpc_interrupt_update(d);
782315a1350SMichael S. Tsirkin     return 0;
783315a1350SMichael S. Tsirkin }
784315a1350SMichael S. Tsirkin 
7858e5e0890SRichard Henderson const VMStateInfo shpc_vmstate_info = {
786315a1350SMichael S. Tsirkin     .name = "shpc",
787315a1350SMichael S. Tsirkin     .get  = shpc_load,
788315a1350SMichael S. Tsirkin     .put  = shpc_save,
789315a1350SMichael S. Tsirkin };
790