aboutsummaryrefslogtreecommitdiff
path: root/hw/char/diva-gsp.c
blob: ecec1f7bb1173ac3baedbb1055ca11d697a1996e (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
/*
 * HP Diva GSP controller
 *
 * The Diva PCI boards are Remote Management cards for PA-RISC machines.
 * They come with built-in 16550A multi UARTs for serial consoles
 * and a mailbox-like memory area for hardware auto-reboot functionality.
 * GSP stands for "Guardian Service Processor". Later products were marketed
 * "Management Processor" (MP).
 *
 * Diva cards are multifunctional cards. The first part, the aux port,
 * is on physical machines not useable but we still try to mimic it here.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Copyright (c) 2025 Helge Deller <deller@gmx.de>
 */

#include "qemu/osdep.h"
#include "qemu/units.h"
#include "hw/char/serial.h"
#include "hw/irq.h"
#include "hw/pci/pci_device.h"
#include "hw/qdev-properties.h"
#include "hw/qdev-properties-system.h"
#include "migration/vmstate.h"

#define PCI_DEVICE_ID_HP_DIVA           0x1048
/* various DIVA GSP cards: */
#define PCI_DEVICE_ID_HP_DIVA_TOSCA1    0x1049
#define PCI_DEVICE_ID_HP_DIVA_TOSCA2    0x104A
#define PCI_DEVICE_ID_HP_DIVA_MAESTRO   0x104B
#define PCI_DEVICE_ID_HP_REO_IOC        0x10f1
#define PCI_DEVICE_ID_HP_DIVA_HALFDOME  0x1223
#define PCI_DEVICE_ID_HP_DIVA_KEYSTONE  0x1226
#define PCI_DEVICE_ID_HP_DIVA_POWERBAR  0x1227
#define PCI_DEVICE_ID_HP_DIVA_EVEREST   0x1282
#define PCI_DEVICE_ID_HP_DIVA_AUX       0x1290
#define PCI_DEVICE_ID_HP_DIVA_RMP3      0x1301
#define PCI_DEVICE_ID_HP_DIVA_HURRICANE 0x132a


#define PCI_SERIAL_MAX_PORTS 4

typedef struct PCIDivaSerialState {
    PCIDevice    dev;
    MemoryRegion membar;        /* for serial ports */
    MemoryRegion mailboxbar;    /* for hardware mailbox */
    uint32_t     subvendor;
    uint32_t     ports;
    char         *name[PCI_SERIAL_MAX_PORTS];
    SerialState  state[PCI_SERIAL_MAX_PORTS];
    uint32_t     level[PCI_SERIAL_MAX_PORTS];
    qemu_irq     *irqs;
    uint8_t      prog_if;
    bool         disable;
} PCIDivaSerialState;

static void diva_pci_exit(PCIDevice *dev)
{
    PCIDivaSerialState *pci = DO_UPCAST(PCIDivaSerialState, dev, dev);
    SerialState *s;
    int i;

    for (i = 0; i < pci->ports; i++) {
        s = pci->state + i;
        qdev_unrealize(DEVICE(s));
        memory_region_del_subregion(&pci->membar, &s->io);
        g_free(pci->name[i]);
    }
    qemu_free_irqs(pci->irqs, pci->ports);
}

static void multi_serial_irq_mux(void *opaque, int n, int level)
{
    PCIDivaSerialState *pci = opaque;
    int i, pending = 0;

    pci->level[n] = level;
    for (i = 0; i < pci->ports; i++) {
        if (pci->level[i]) {
            pending = 1;
        }
    }
    pci_set_irq(&pci->dev, pending);
}

struct diva_info {
    unsigned int nports:4; /* number of serial ports */
    unsigned int omask:12; /* offset mask: BIT(1) -> offset 8 */
};

static struct diva_info diva_get_diva_info(PCIDeviceClass *pc)
{
    switch (pc->subsystem_id) {
    case PCI_DEVICE_ID_HP_DIVA_POWERBAR:
    case PCI_DEVICE_ID_HP_DIVA_HURRICANE:
        return (struct diva_info) { .nports = 1,
                        .omask = BIT(0) };
    case PCI_DEVICE_ID_HP_DIVA_TOSCA2:
        return (struct diva_info) { .nports = 2,
                        .omask = BIT(0) | BIT(1) };
    case PCI_DEVICE_ID_HP_DIVA_TOSCA1:
    case PCI_DEVICE_ID_HP_DIVA_HALFDOME:
    case PCI_DEVICE_ID_HP_DIVA_KEYSTONE:
        return (struct diva_info) { .nports = 3,
                        .omask = BIT(0) | BIT(1) | BIT(2) };
    case PCI_DEVICE_ID_HP_DIVA_EVEREST: /* e.g. in rp3410 */
        return (struct diva_info) { .nports = 3,
                        .omask = BIT(0) | BIT(2) | BIT(7) };
    case PCI_DEVICE_ID_HP_DIVA_MAESTRO:
        return (struct diva_info) { .nports = 4,
                        .omask = BIT(0) | BIT(1) | BIT(2) | BIT(7) };
    }
    g_assert_not_reached();
}


static void diva_pci_realize(PCIDevice *dev, Error **errp)
{
    PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
    PCIDivaSerialState *pci = DO_UPCAST(PCIDivaSerialState, dev, dev);
    SerialState *s;
    struct diva_info di = diva_get_diva_info(pc);
    size_t i, offset = 0;
    size_t portmask = di.omask;

    pci->dev.config[PCI_CLASS_PROG] = pci->prog_if;
    pci->dev.config[PCI_INTERRUPT_PIN] = 0x01;
    memory_region_init(&pci->membar, OBJECT(pci), "serial_ports", 4096);
    pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &pci->membar);
    pci->irqs = qemu_allocate_irqs(multi_serial_irq_mux, pci, di.nports);

    for (i = 0; i < di.nports; i++) {
        s = pci->state + i;
        if (!qdev_realize(DEVICE(s), NULL, errp)) {
            diva_pci_exit(dev);
            return;
        }
        s->irq = pci->irqs[i];
        pci->name[i] = g_strdup_printf("uart #%zu", i + 1);
        memory_region_init_io(&s->io, OBJECT(pci), &serial_io_ops, s,
                              pci->name[i], 8);

        /* calculate offset of given port based on bitmask */
        while ((portmask & BIT(0)) == 0) {
            offset += 8;
            portmask >>= 1;
        }
        memory_region_add_subregion(&pci->membar, offset, &s->io);
        offset += 8;
        portmask >>= 1;
        pci->ports++;
    }

    /* mailbox bar */
    memory_region_init(&pci->mailboxbar, OBJECT(pci), "mailbox", 128 * KiB);
    pci_register_bar(&pci->dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY |
                     PCI_BASE_ADDRESS_MEM_PREFETCH, &pci->mailboxbar);
}

static const VMStateDescription vmstate_pci_diva = {
    .name = "pci-diva-serial",
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (const VMStateField[]) {
        VMSTATE_PCI_DEVICE(dev, PCIDivaSerialState),
        VMSTATE_STRUCT_ARRAY(state, PCIDivaSerialState, PCI_SERIAL_MAX_PORTS,
                             0, vmstate_serial, SerialState),
        VMSTATE_UINT32_ARRAY(level, PCIDivaSerialState, PCI_SERIAL_MAX_PORTS),
        VMSTATE_BOOL(disable, PCIDivaSerialState),
        VMSTATE_END_OF_LIST()
    }
};

static const Property diva_serial_properties[] = {
    DEFINE_PROP_BOOL("disable",  PCIDivaSerialState, disable, false),
    DEFINE_PROP_CHR("chardev1",  PCIDivaSerialState, state[0].chr),
    DEFINE_PROP_CHR("chardev2",  PCIDivaSerialState, state[1].chr),
    DEFINE_PROP_CHR("chardev3",  PCIDivaSerialState, state[2].chr),
    DEFINE_PROP_CHR("chardev4",  PCIDivaSerialState, state[3].chr),
    DEFINE_PROP_UINT8("prog_if",  PCIDivaSerialState, prog_if, 0x02),
    DEFINE_PROP_UINT32("subvendor", PCIDivaSerialState, subvendor,
                                    PCI_DEVICE_ID_HP_DIVA_TOSCA1),
};

static void diva_serial_class_initfn(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
    pc->realize = diva_pci_realize;
    pc->exit = diva_pci_exit;
    pc->vendor_id = PCI_VENDOR_ID_HP;
    pc->device_id = PCI_DEVICE_ID_HP_DIVA;
    pc->subsystem_vendor_id = PCI_VENDOR_ID_HP;
    pc->subsystem_id = PCI_DEVICE_ID_HP_DIVA_TOSCA1;
    pc->revision = 3;
    pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL;
    dc->vmsd = &vmstate_pci_diva;
    device_class_set_props(dc, diva_serial_properties);
    set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}

static void diva_serial_init(Object *o)
{
    PCIDevice *dev = PCI_DEVICE(o);
    PCIDivaSerialState *pms = DO_UPCAST(PCIDivaSerialState, dev, dev);
    struct diva_info di = diva_get_diva_info(PCI_DEVICE_GET_CLASS(dev));
    size_t i;

    for (i = 0; i < di.nports; i++) {
        object_initialize_child(o, "serial[*]", &pms->state[i], TYPE_SERIAL);
    }
}


/* Diva-aux is the driver for portion 0 of the multifunction PCI device */

struct DivaAuxState {
    PCIDevice dev;
    MemoryRegion mem;
    qemu_irq irq;
};

#define TYPE_DIVA_AUX "diva-aux"
OBJECT_DECLARE_SIMPLE_TYPE(DivaAuxState, DIVA_AUX)

static void diva_aux_realize(PCIDevice *dev, Error **errp)
{
    DivaAuxState *pci = DO_UPCAST(DivaAuxState, dev, dev);

    pci->dev.config[PCI_CLASS_PROG] = 0x02;
    pci->dev.config[PCI_INTERRUPT_PIN] = 0x01;
    pci->irq = pci_allocate_irq(&pci->dev);

    memory_region_init(&pci->mem, OBJECT(pci), "mem", 16);
    pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &pci->mem);
}

static void diva_aux_exit(PCIDevice *dev)
{
    DivaAuxState *pci = DO_UPCAST(DivaAuxState, dev, dev);
    qemu_free_irq(pci->irq);
}

static void diva_aux_class_initfn(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
    pc->realize = diva_aux_realize;
    pc->exit = diva_aux_exit;
    pc->vendor_id = PCI_VENDOR_ID_HP;
    pc->device_id = PCI_DEVICE_ID_HP_DIVA_AUX;
    pc->subsystem_vendor_id = PCI_VENDOR_ID_HP;
    pc->subsystem_id = 0x1291;
    pc->revision = 1;
    pc->class_id = PCI_CLASS_COMMUNICATION_MULTISERIAL;
    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
    dc->user_creatable = false;
}

static void diva_aux_init(Object *o)
{
}

static const TypeInfo diva_aux_info = {
    .name          = TYPE_DIVA_AUX,
    .parent        = TYPE_PCI_DEVICE,
    .instance_size = sizeof(DivaAuxState),
    .instance_init = diva_aux_init,
    .class_init    = diva_aux_class_initfn,
    .interfaces = (InterfaceInfo[]) {
        { INTERFACE_CONVENTIONAL_PCI_DEVICE },
        { },
    },
};



static const TypeInfo diva_serial_pci_info = {
    .name          = "diva-gsp",
    .parent        = TYPE_PCI_DEVICE,
    .instance_size = sizeof(PCIDivaSerialState),
    .instance_init = diva_serial_init,
    .class_init    = diva_serial_class_initfn,
    .interfaces = (InterfaceInfo[]) {
        { INTERFACE_CONVENTIONAL_PCI_DEVICE },
        { },
    },
};

static void diva_pci_register_type(void)
{
    type_register_static(&diva_serial_pci_info);
    type_register_static(&diva_aux_info);
}

type_init(diva_pci_register_type)