]>
Commit | Line | Data |
---|---|---|
e4e6db52 DHB |
1 | /* |
2 | * QEMU PowerPC PowerNV Proxy PHB model | |
3 | * | |
4 | * Copyright (c) 2022, IBM Corporation. | |
5 | * | |
6 | * This code is licensed under the GPL version 2 or later. See the | |
7 | * COPYING file in the top-level directory. | |
8 | */ | |
9 | ||
10 | #include "qemu/osdep.h" | |
11 | #include "qemu/log.h" | |
12 | #include "qapi/visitor.h" | |
13 | #include "qapi/error.h" | |
14 | #include "hw/pci-host/pnv_phb.h" | |
15 | #include "hw/pci-host/pnv_phb3.h" | |
16 | #include "hw/pci-host/pnv_phb4.h" | |
17 | #include "hw/ppc/pnv.h" | |
18 | #include "hw/qdev-properties.h" | |
19 | #include "qom/object.h" | |
892c3ad0 | 20 | #include "sysemu/sysemu.h" |
e4e6db52 | 21 | |
ba47c3a4 DHB |
22 | |
23 | /* | |
3466bb50 DHB |
24 | * Set the QOM parent and parent bus of an object child. If the device |
25 | * state associated with the child has an id, use it as QOM id. | |
26 | * Otherwise use object_typename[index] as QOM id. | |
27 | * | |
28 | * This helper does both operations at the same time because seting | |
29 | * a new QOM child will erase the bus parent of the device. This happens | |
30 | * because object_unparent() will call object_property_del_child(), | |
31 | * which in turn calls the property release callback prop->release if | |
32 | * it's defined. In our case this callback is set to | |
33 | * object_finalize_child_property(), which was assigned during the | |
34 | * first object_property_add_child() call. This callback will end up | |
35 | * calling device_unparent(), and this function removes the device | |
36 | * from its parent bus. | |
37 | * | |
38 | * The QOM and parent bus to be set arenĀ“t necessarily related, so | |
39 | * let's receive both as arguments. | |
ba47c3a4 | 40 | */ |
3466bb50 DHB |
41 | static bool pnv_parent_fixup(Object *parent, BusState *parent_bus, |
42 | Object *child, int index, | |
43 | Error **errp) | |
ba47c3a4 DHB |
44 | { |
45 | g_autofree char *default_id = | |
46 | g_strdup_printf("%s[%d]", object_get_typename(child), index); | |
47 | const char *dev_id = DEVICE(child)->id; | |
48 | ||
49 | if (child->parent == parent) { | |
3466bb50 | 50 | return true; |
ba47c3a4 DHB |
51 | } |
52 | ||
53 | object_ref(child); | |
54 | object_unparent(child); | |
55 | object_property_add_child(parent, dev_id ? dev_id : default_id, child); | |
56 | object_unref(child); | |
ba47c3a4 | 57 | |
3466bb50 DHB |
58 | if (!qdev_set_parent_bus(DEVICE(child), parent_bus, errp)) { |
59 | return false; | |
ba47c3a4 | 60 | } |
3466bb50 DHB |
61 | |
62 | return true; | |
ba47c3a4 DHB |
63 | } |
64 | ||
ba47c3a4 DHB |
65 | /* |
66 | * User created devices won't have the initial setup that default | |
67 | * devices have. This setup consists of assigning a parent device | |
68 | * (chip for PHB3, PEC for PHB4/5) that will be the QOM/bus parent | |
69 | * of the PHB. | |
70 | */ | |
71 | static bool pnv_phb_user_device_init(PnvPHB *phb, Error **errp) | |
72 | { | |
73 | PnvMachineState *pnv = PNV_MACHINE(qdev_get_machine()); | |
74 | PnvChip *chip = pnv_get_chip(pnv, phb->chip_id); | |
75 | Object *parent = NULL; | |
76 | ||
77 | if (!chip) { | |
78 | error_setg(errp, "invalid chip id: %d", phb->chip_id); | |
79 | return false; | |
80 | } | |
81 | ||
82 | parent = pnv_chip_add_phb(chip, phb, errp); | |
83 | if (!parent) { | |
84 | return false; | |
85 | } | |
86 | ||
87 | /* | |
88 | * Reparent user created devices to the chip to build | |
89 | * correctly the device tree. pnv_xscom_dt() needs every | |
90 | * PHB to be a child of the chip to build the DT correctly. | |
91 | */ | |
3466bb50 DHB |
92 | if (!pnv_parent_fixup(parent, qdev_get_parent_bus(DEVICE(chip)), |
93 | OBJECT(phb), phb->phb_id, errp)) { | |
94 | return false; | |
95 | } | |
ba47c3a4 DHB |
96 | |
97 | return true; | |
98 | } | |
99 | ||
e4e6db52 DHB |
100 | static void pnv_phb_realize(DeviceState *dev, Error **errp) |
101 | { | |
102 | PnvPHB *phb = PNV_PHB(dev); | |
103 | PCIHostState *pci = PCI_HOST_BRIDGE(dev); | |
104 | g_autofree char *phb_typename = NULL; | |
e4e6db52 DHB |
105 | |
106 | if (!phb->version) { | |
107 | error_setg(errp, "version not specified"); | |
108 | return; | |
109 | } | |
110 | ||
111 | switch (phb->version) { | |
112 | case 3: | |
113 | phb_typename = g_strdup(TYPE_PNV_PHB3); | |
e4e6db52 DHB |
114 | break; |
115 | case 4: | |
116 | phb_typename = g_strdup(TYPE_PNV_PHB4); | |
e4e6db52 DHB |
117 | break; |
118 | case 5: | |
119 | phb_typename = g_strdup(TYPE_PNV_PHB5); | |
e4e6db52 DHB |
120 | break; |
121 | default: | |
122 | g_assert_not_reached(); | |
123 | } | |
124 | ||
125 | phb->backend = object_new(phb_typename); | |
126 | object_property_add_child(OBJECT(dev), "phb-backend", phb->backend); | |
127 | ||
128 | /* Passthrough child device properties to the proxy device */ | |
129 | object_property_set_uint(phb->backend, "index", phb->phb_id, errp); | |
130 | object_property_set_uint(phb->backend, "chip-id", phb->chip_id, errp); | |
131 | object_property_set_link(phb->backend, "phb-base", OBJECT(phb), errp); | |
132 | ||
ba47c3a4 DHB |
133 | /* |
134 | * Handle user created devices. User devices will not have a | |
135 | * pointer to a chip (PHB3) and a PEC (PHB4/5). | |
136 | */ | |
137 | if (!phb->chip && !phb->pec) { | |
138 | if (!pnv_phb_user_device_init(phb, errp)) { | |
139 | return; | |
140 | } | |
141 | } | |
142 | ||
e4e6db52 DHB |
143 | if (phb->version == 3) { |
144 | object_property_set_link(phb->backend, "chip", | |
145 | OBJECT(phb->chip), errp); | |
146 | } else { | |
147 | object_property_set_link(phb->backend, "pec", OBJECT(phb->pec), errp); | |
148 | } | |
149 | ||
150 | if (!qdev_realize(DEVICE(phb->backend), NULL, errp)) { | |
151 | return; | |
152 | } | |
153 | ||
154 | if (phb->version == 3) { | |
155 | pnv_phb3_bus_init(dev, PNV_PHB3(phb->backend)); | |
fe5bfd4b DHB |
156 | } else { |
157 | pnv_phb4_bus_init(dev, PNV_PHB4(phb->backend)); | |
e4e6db52 DHB |
158 | } |
159 | ||
6a1e1ce2 DHB |
160 | if (defaults_enabled()) { |
161 | PCIDevice *root = pci_new(PCI_DEVFN(0, 0), TYPE_PNV_PHB_ROOT_PORT); | |
892c3ad0 | 162 | |
6a1e1ce2 DHB |
163 | pci_realize_and_unref(root, pci->bus, errp); |
164 | } | |
e4e6db52 DHB |
165 | } |
166 | ||
167 | static const char *pnv_phb_root_bus_path(PCIHostState *host_bridge, | |
168 | PCIBus *rootbus) | |
169 | { | |
170 | PnvPHB *phb = PNV_PHB(host_bridge); | |
171 | ||
172 | snprintf(phb->bus_path, sizeof(phb->bus_path), "00%02x:%02x", | |
173 | phb->chip_id, phb->phb_id); | |
174 | return phb->bus_path; | |
175 | } | |
176 | ||
177 | static Property pnv_phb_properties[] = { | |
178 | DEFINE_PROP_UINT32("index", PnvPHB, phb_id, 0), | |
179 | DEFINE_PROP_UINT32("chip-id", PnvPHB, chip_id, 0), | |
180 | DEFINE_PROP_UINT32("version", PnvPHB, version, 0), | |
181 | ||
182 | DEFINE_PROP_LINK("chip", PnvPHB, chip, TYPE_PNV_CHIP, PnvChip *), | |
183 | ||
184 | DEFINE_PROP_LINK("pec", PnvPHB, pec, TYPE_PNV_PHB4_PEC, | |
185 | PnvPhb4PecState *), | |
186 | ||
187 | DEFINE_PROP_END_OF_LIST(), | |
188 | }; | |
189 | ||
190 | static void pnv_phb_class_init(ObjectClass *klass, void *data) | |
191 | { | |
192 | PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(klass); | |
193 | DeviceClass *dc = DEVICE_CLASS(klass); | |
194 | ||
195 | hc->root_bus_path = pnv_phb_root_bus_path; | |
196 | dc->realize = pnv_phb_realize; | |
197 | device_class_set_props(dc, pnv_phb_properties); | |
198 | set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); | |
892c3ad0 | 199 | dc->user_creatable = true; |
e4e6db52 DHB |
200 | } |
201 | ||
f4c636b0 | 202 | static void pnv_phb_root_port_reset_hold(Object *obj) |
e4e6db52 | 203 | { |
f4c636b0 PM |
204 | PCIERootPortClass *rpc = PCIE_ROOT_PORT_GET_CLASS(obj); |
205 | PnvPHBRootPort *phb_rp = PNV_PHB_ROOT_PORT(obj); | |
206 | PCIDevice *d = PCI_DEVICE(obj); | |
5ba76b61 | 207 | uint8_t *conf = d->config; |
e4e6db52 | 208 | |
f4c636b0 PM |
209 | if (rpc->parent_phases.hold) { |
210 | rpc->parent_phases.hold(obj); | |
211 | } | |
5ba76b61 DHB |
212 | |
213 | if (phb_rp->version == 3) { | |
214 | return; | |
215 | } | |
216 | ||
217 | /* PHB4 and later requires these extra reset steps */ | |
218 | pci_byte_test_and_set_mask(conf + PCI_IO_BASE, | |
219 | PCI_IO_RANGE_MASK & 0xff); | |
220 | pci_byte_test_and_clear_mask(conf + PCI_IO_LIMIT, | |
221 | PCI_IO_RANGE_MASK & 0xff); | |
222 | pci_set_word(conf + PCI_MEMORY_BASE, 0); | |
223 | pci_set_word(conf + PCI_MEMORY_LIMIT, 0xfff0); | |
224 | pci_set_word(conf + PCI_PREF_MEMORY_BASE, 0x1); | |
225 | pci_set_word(conf + PCI_PREF_MEMORY_LIMIT, 0xfff1); | |
226 | pci_set_long(conf + PCI_PREF_BASE_UPPER32, 0x1); /* Hack */ | |
227 | pci_set_long(conf + PCI_PREF_LIMIT_UPPER32, 0xffffffff); | |
228 | pci_config_set_interrupt_pin(conf, 0); | |
229 | } | |
230 | ||
231 | static void pnv_phb_root_port_realize(DeviceState *dev, Error **errp) | |
232 | { | |
233 | PCIERootPortClass *rpc = PCIE_ROOT_PORT_GET_CLASS(dev); | |
234 | PnvPHBRootPort *phb_rp = PNV_PHB_ROOT_PORT(dev); | |
c2f3f78a | 235 | PCIBus *bus = PCI_BUS(qdev_get_parent_bus(dev)); |
5ba76b61 DHB |
236 | PCIDevice *pci = PCI_DEVICE(dev); |
237 | uint16_t device_id = 0; | |
238 | Error *local_err = NULL; | |
c2f3f78a DHB |
239 | int chip_id, index; |
240 | ||
6a1e1ce2 DHB |
241 | /* |
242 | * 'index' will be used both as a PCIE slot value and to calculate | |
243 | * QOM id. 'chip_id' is going to be used as PCIE chassis for the | |
244 | * root port. | |
245 | */ | |
ec565134 TH |
246 | chip_id = object_property_get_int(OBJECT(bus), "chip-id", &local_err); |
247 | if (local_err) { | |
248 | error_propagate(errp, local_err); | |
249 | return; | |
250 | } | |
251 | index = object_property_get_int(OBJECT(bus), "phb-id", &local_err); | |
252 | if (local_err) { | |
253 | error_propagate(errp, local_err); | |
254 | return; | |
255 | } | |
c2f3f78a DHB |
256 | |
257 | /* Set unique chassis/slot values for the root port */ | |
258 | qdev_prop_set_uint8(dev, "chassis", chip_id); | |
259 | qdev_prop_set_uint16(dev, "slot", index); | |
5ba76b61 | 260 | |
6a1e1ce2 DHB |
261 | /* |
262 | * User created root ports are QOM parented to one of | |
263 | * the peripheral containers but it's already at the right | |
264 | * parent bus. Change the QOM parent to be the same as the | |
265 | * parent bus it's already assigned to. | |
266 | */ | |
267 | if (!pnv_parent_fixup(OBJECT(bus), BUS(bus), OBJECT(dev), | |
268 | index, errp)) { | |
269 | return; | |
270 | } | |
271 | ||
5ba76b61 DHB |
272 | rpc->parent_realize(dev, &local_err); |
273 | if (local_err) { | |
274 | error_propagate(errp, local_err); | |
275 | return; | |
276 | } | |
277 | ||
278 | switch (phb_rp->version) { | |
279 | case 3: | |
280 | device_id = PNV_PHB3_DEVICE_ID; | |
281 | break; | |
282 | case 4: | |
283 | device_id = PNV_PHB4_DEVICE_ID; | |
284 | break; | |
285 | case 5: | |
286 | device_id = PNV_PHB5_DEVICE_ID; | |
287 | break; | |
288 | default: | |
289 | g_assert_not_reached(); | |
290 | } | |
291 | ||
292 | pci_config_set_device_id(pci->config, device_id); | |
293 | pci_config_set_interrupt_pin(pci->config, 0); | |
294 | } | |
295 | ||
296 | static Property pnv_phb_root_port_properties[] = { | |
297 | DEFINE_PROP_UINT32("version", PnvPHBRootPort, version, 0), | |
298 | ||
299 | DEFINE_PROP_END_OF_LIST(), | |
300 | }; | |
301 | ||
302 | static void pnv_phb_root_port_class_init(ObjectClass *klass, void *data) | |
303 | { | |
304 | DeviceClass *dc = DEVICE_CLASS(klass); | |
f4c636b0 | 305 | ResettableClass *rc = RESETTABLE_CLASS(klass); |
5ba76b61 DHB |
306 | PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); |
307 | PCIERootPortClass *rpc = PCIE_ROOT_PORT_CLASS(klass); | |
308 | ||
309 | dc->desc = "IBM PHB PCIE Root Port"; | |
310 | ||
311 | device_class_set_props(dc, pnv_phb_root_port_properties); | |
312 | device_class_set_parent_realize(dc, pnv_phb_root_port_realize, | |
313 | &rpc->parent_realize); | |
f4c636b0 PM |
314 | resettable_class_set_parent_phases(rc, NULL, pnv_phb_root_port_reset_hold, |
315 | NULL, &rpc->parent_phases); | |
892c3ad0 | 316 | dc->user_creatable = true; |
5ba76b61 DHB |
317 | |
318 | k->vendor_id = PCI_VENDOR_ID_IBM; | |
319 | /* device_id will be written during realize() */ | |
320 | k->device_id = 0; | |
321 | k->revision = 0; | |
322 | ||
323 | rpc->exp_offset = 0x48; | |
324 | rpc->aer_offset = 0x100; | |
325 | } | |
326 | ||
327 | static const TypeInfo pnv_phb_type_info = { | |
328 | .name = TYPE_PNV_PHB, | |
329 | .parent = TYPE_PCIE_HOST_BRIDGE, | |
330 | .instance_size = sizeof(PnvPHB), | |
331 | .class_init = pnv_phb_class_init, | |
332 | }; | |
333 | ||
334 | static const TypeInfo pnv_phb_root_port_info = { | |
335 | .name = TYPE_PNV_PHB_ROOT_PORT, | |
336 | .parent = TYPE_PCIE_ROOT_PORT, | |
337 | .instance_size = sizeof(PnvPHBRootPort), | |
338 | .class_init = pnv_phb_root_port_class_init, | |
339 | }; | |
340 | ||
341 | static void pnv_phb_register_types(void) | |
342 | { | |
e4e6db52 | 343 | type_register_static(&pnv_phb_type_info); |
5ba76b61 | 344 | type_register_static(&pnv_phb_root_port_info); |
e4e6db52 | 345 | } |
5ba76b61 DHB |
346 | |
347 | type_init(pnv_phb_register_types) |