aboutsummaryrefslogtreecommitdiff
path: root/hw/pci-host/pnv_phb.c
blob: 157c00782ce8f8e8d3479225c199914bb7ea6913 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/*
 * QEMU PowerPC PowerNV Proxy PHB model
 *
 * Copyright (c) 2022, IBM Corporation.
 *
 * This code is licensed under the GPL version 2 or later. See the
 * COPYING file in the top-level directory.
 */

#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qapi/visitor.h"
#include "qapi/error.h"
#include "hw/pci-host/pnv_phb.h"
#include "hw/pci-host/pnv_phb3.h"
#include "hw/pci-host/pnv_phb4.h"
#include "hw/ppc/pnv.h"
#include "hw/qdev-properties.h"
#include "qom/object.h"
#include "sysemu/sysemu.h"


/*
 * Set the QOM parent and parent bus of an object child. If the device
 * state associated with the child has an id, use it as QOM id.
 * Otherwise use object_typename[index] as QOM id.
 *
 * This helper does both operations at the same time because setting
 * a new QOM child will erase the bus parent of the device. This happens
 * because object_unparent() will call object_property_del_child(),
 * which in turn calls the property release callback prop->release if
 * it's defined. In our case this callback is set to
 * object_finalize_child_property(), which was assigned during the
 * first object_property_add_child() call. This callback will end up
 * calling device_unparent(), and this function removes the device
 * from its parent bus.
 *
 * The QOM and parent bus to be set arenĀ“t necessarily related, so
 * let's receive both as arguments.
 */
static bool pnv_parent_fixup(Object *parent, BusState *parent_bus,
                             Object *child, int index,
                             Error **errp)
{
    g_autofree char *default_id =
        g_strdup_printf("%s[%d]", object_get_typename(child), index);
    const char *dev_id = DEVICE(child)->id;

    if (child->parent == parent) {
        return true;
    }

    object_ref(child);
    object_unparent(child);
    object_property_add_child(parent, dev_id ? dev_id : default_id, child);
    object_unref(child);

    if (!qdev_set_parent_bus(DEVICE(child), parent_bus, errp)) {
        return false;
    }

    return true;
}

static Object *pnv_phb_user_get_parent(PnvChip *chip, PnvPHB *phb, Error **errp)
{
    if (phb->version == 3) {
        return OBJECT(pnv_chip_add_phb(chip, phb));
    } else {
        return OBJECT(pnv_pec_add_phb(chip, phb, errp));
    }
}

/*
 * User created devices won't have the initial setup that default
 * devices have. This setup consists of assigning a parent device
 * (chip for PHB3, PEC for PHB4/5) that will be the QOM/bus parent
 * of the PHB.
 */
static bool pnv_phb_user_device_init(PnvPHB *phb, Error **errp)
{
    PnvMachineState *pnv = PNV_MACHINE(qdev_get_machine());
    PnvChip *chip = pnv_get_chip(pnv, phb->chip_id);
    Object *parent = NULL;

    if (!chip) {
        error_setg(errp, "invalid chip id: %d", phb->chip_id);
        return false;
    }

    parent = pnv_phb_user_get_parent(chip, phb, errp);
    if (!parent) {
        return false;
    }

    /*
     * Reparent user created devices to the chip to build
     * correctly the device tree. pnv_xscom_dt() needs every
     * PHB to be a child of the chip to build the DT correctly.
     */
    if (!pnv_parent_fixup(parent, qdev_get_parent_bus(DEVICE(chip)),
                          OBJECT(phb), phb->phb_id, errp)) {
        return false;
    }

    return true;
}

static void pnv_phb_realize(DeviceState *dev, Error **errp)
{
    PnvPHB *phb = PNV_PHB(dev);
    PCIHostState *pci = PCI_HOST_BRIDGE(dev);
    g_autofree char *phb_typename = NULL;

    if (!phb->version) {
        error_setg(errp, "version not specified");
        return;
    }

    switch (phb->version) {
    case 3:
        phb_typename = g_strdup(TYPE_PNV_PHB3);
        break;
    case 4:
        phb_typename = g_strdup(TYPE_PNV_PHB4);
        break;
    case 5:
        phb_typename = g_strdup(TYPE_PNV_PHB5);
        break;
    default:
        g_assert_not_reached();
    }

    phb->backend = object_new(phb_typename);
    object_property_add_child(OBJECT(dev), "phb-backend", phb->backend);

    /* Passthrough child device properties to the proxy device */
    object_property_set_uint(phb->backend, "index", phb->phb_id, errp);
    object_property_set_uint(phb->backend, "chip-id", phb->chip_id, errp);
    object_property_set_link(phb->backend, "phb-base", OBJECT(phb), errp);

    /*
     * Handle user created devices. User devices will not have a
     * pointer to a chip (PHB3) and a PEC (PHB4/5).
     */
    if (!phb->chip && !phb->pec) {
        if (!pnv_phb_user_device_init(phb, errp)) {
            return;
        }
    }

    if (phb->version == 3) {
        object_property_set_link(phb->backend, "chip",
                                 OBJECT(phb->chip), errp);
    } else {
        object_property_set_link(phb->backend, "pec", OBJECT(phb->pec), errp);
    }

    if (!qdev_realize(DEVICE(phb->backend), NULL, errp)) {
        return;
    }

    if (phb->version == 3) {
        pnv_phb3_bus_init(dev, PNV_PHB3(phb->backend));
    } else {
        pnv_phb4_bus_init(dev, PNV_PHB4(phb->backend));
    }

    if (defaults_enabled()) {
        PCIDevice *root = pci_new(PCI_DEVFN(0, 0), TYPE_PNV_PHB_ROOT_PORT);

        pci_realize_and_unref(root, pci->bus, errp);
    }
}

static const char *pnv_phb_root_bus_path(PCIHostState *host_bridge,
                                         PCIBus *rootbus)
{
    PnvPHB *phb = PNV_PHB(host_bridge);

    snprintf(phb->bus_path, sizeof(phb->bus_path), "00%02x:%02x",
             phb->chip_id, phb->phb_id);
    return phb->bus_path;
}

static Property pnv_phb_properties[] = {
    DEFINE_PROP_UINT32("index", PnvPHB, phb_id, 0),
    DEFINE_PROP_UINT32("chip-id", PnvPHB, chip_id, 0),
    DEFINE_PROP_UINT32("version", PnvPHB, version, 0),

    DEFINE_PROP_LINK("chip", PnvPHB, chip, TYPE_PNV_CHIP, PnvChip *),

    DEFINE_PROP_LINK("pec", PnvPHB, pec, TYPE_PNV_PHB4_PEC,
                     PnvPhb4PecState *),

    DEFINE_PROP_END_OF_LIST(),
};

static void pnv_phb_class_init(ObjectClass *klass, void *data)
{
    PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(klass);
    DeviceClass *dc = DEVICE_CLASS(klass);

    hc->root_bus_path = pnv_phb_root_bus_path;
    dc->realize = pnv_phb_realize;
    device_class_set_props(dc, pnv_phb_properties);
    set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
    dc->user_creatable = true;
}

static void pnv_phb_root_port_reset_hold(Object *obj)
{
    PCIERootPortClass *rpc = PCIE_ROOT_PORT_GET_CLASS(obj);
    PnvPHBRootPort *phb_rp = PNV_PHB_ROOT_PORT(obj);
    PCIDevice *d = PCI_DEVICE(obj);
    uint8_t *conf = d->config;

    if (rpc->parent_phases.hold) {
        rpc->parent_phases.hold(obj);
    }

    if (phb_rp->version == 3) {
        return;
    }

    /* PHB4 and later requires these extra reset steps */
    pci_byte_test_and_set_mask(conf + PCI_IO_BASE,
                               PCI_IO_RANGE_MASK & 0xff);
    pci_byte_test_and_clear_mask(conf + PCI_IO_LIMIT,
                                 PCI_IO_RANGE_MASK & 0xff);
    pci_set_word(conf + PCI_MEMORY_BASE, 0);
    pci_set_word(conf + PCI_MEMORY_LIMIT, 0xfff0);
    pci_set_word(conf + PCI_PREF_MEMORY_BASE, 0x1);
    pci_set_word(conf + PCI_PREF_MEMORY_LIMIT, 0xfff1);
    pci_set_long(conf + PCI_PREF_BASE_UPPER32, 0x1); /* Hack */
    pci_set_long(conf + PCI_PREF_LIMIT_UPPER32, 0xffffffff);
    pci_config_set_interrupt_pin(conf, 0);
}

static void pnv_phb_root_port_realize(DeviceState *dev, Error **errp)
{
    PCIERootPortClass *rpc = PCIE_ROOT_PORT_GET_CLASS(dev);
    PnvPHBRootPort *phb_rp = PNV_PHB_ROOT_PORT(dev);
    PCIBus *bus = PCI_BUS(qdev_get_parent_bus(dev));
    PCIDevice *pci = PCI_DEVICE(dev);
    uint16_t device_id = 0;
    Error *local_err = NULL;
    int chip_id, index;

    /*
     * 'index' will be used both as a PCIE slot value and to calculate
     * QOM id. 'chip_id' is going to be used as PCIE chassis for the
     * root port.
     */
    chip_id = object_property_get_int(OBJECT(bus), "chip-id", &local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        return;
    }
    index = object_property_get_int(OBJECT(bus), "phb-id", &local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        return;
    }

    /* Set unique chassis/slot values for the root port */
    qdev_prop_set_uint8(dev, "chassis", chip_id);
    qdev_prop_set_uint16(dev, "slot", index);

    /*
     * User created root ports are QOM parented to one of
     * the peripheral containers but it's already at the right
     * parent bus. Change the QOM parent to be the same as the
     * parent bus it's already assigned to.
     */
    if (!pnv_parent_fixup(OBJECT(bus), BUS(bus), OBJECT(dev),
                          index, errp)) {
        return;
    }

    rpc->parent_realize(dev, &local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        return;
    }

    switch (phb_rp->version) {
    case 3:
        device_id = PNV_PHB3_DEVICE_ID;
        break;
    case 4:
        device_id = PNV_PHB4_DEVICE_ID;
        break;
    case 5:
        device_id = PNV_PHB5_DEVICE_ID;
        break;
    default:
        g_assert_not_reached();
    }

    pci_config_set_device_id(pci->config, device_id);
    pci_config_set_interrupt_pin(pci->config, 0);
}

static Property pnv_phb_root_port_properties[] = {
    DEFINE_PROP_UINT32("version", PnvPHBRootPort, version, 0),

    DEFINE_PROP_END_OF_LIST(),
};

static void pnv_phb_root_port_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    ResettableClass *rc = RESETTABLE_CLASS(klass);
    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
    PCIERootPortClass *rpc = PCIE_ROOT_PORT_CLASS(klass);

    dc->desc     = "IBM PHB PCIE Root Port";

    device_class_set_props(dc, pnv_phb_root_port_properties);
    device_class_set_parent_realize(dc, pnv_phb_root_port_realize,
                                    &rpc->parent_realize);
    resettable_class_set_parent_phases(rc, NULL, pnv_phb_root_port_reset_hold,
                                       NULL, &rpc->parent_phases);
    dc->user_creatable = true;

    k->vendor_id = PCI_VENDOR_ID_IBM;
    /* device_id will be written during realize() */
    k->device_id = 0;
    k->revision  = 0;

    rpc->exp_offset = 0x48;
    rpc->aer_offset = 0x100;
}

static const TypeInfo pnv_phb_type_info = {
    .name          = TYPE_PNV_PHB,
    .parent        = TYPE_PCIE_HOST_BRIDGE,
    .instance_size = sizeof(PnvPHB),
    .class_init    = pnv_phb_class_init,
};

static const TypeInfo pnv_phb_root_port_info = {
    .name          = TYPE_PNV_PHB_ROOT_PORT,
    .parent        = TYPE_PCIE_ROOT_PORT,
    .instance_size = sizeof(PnvPHBRootPort),
    .class_init    = pnv_phb_root_port_class_init,
};

static void pnv_phb_register_types(void)
{
    type_register_static(&pnv_phb_type_info);
    type_register_static(&pnv_phb_root_port_info);
}

type_init(pnv_phb_register_types)