aboutsummaryrefslogtreecommitdiff
path: root/hw/core/platform-bus.c
blob: 0f052b3338a728c02792eb3e2f00e263e4fc2115 (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
/*
 *  Platform Bus device to support dynamic Sysbus devices
 *
 * Copyright (C) 2014 Freescale Semiconductor, Inc. All rights reserved.
 *
 * Author: Alexander Graf, <agraf@suse.de>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */

#include "hw/platform-bus.h"
#include "monitor/monitor.h"
#include "exec/address-spaces.h"
#include "sysemu/sysemu.h"


/*
 * Returns the PlatformBus IRQ number for a SysBusDevice irq number or -1 if
 * the IRQ is not mapped on this Platform bus.
 */
int platform_bus_get_irqn(PlatformBusDevice *pbus, SysBusDevice *sbdev,
                          int n)
{
    qemu_irq sbirq = sysbus_get_connected_irq(sbdev, n);
    int i;

    for (i = 0; i < pbus->num_irqs; i++) {
        if (pbus->irqs[i] == sbirq) {
            return i;
        }
    }

    /* IRQ not mapped on platform bus */
    return -1;
}

/*
 * Returns the PlatformBus MMIO region offset for Region n of a SysBusDevice or
 * -1 if the region is not mapped on this Platform bus.
 */
hwaddr platform_bus_get_mmio_addr(PlatformBusDevice *pbus, SysBusDevice *sbdev,
                                  int n)
{
    MemoryRegion *pbus_mr = &pbus->mmio;
    MemoryRegion *sbdev_mr = sysbus_mmio_get_region(sbdev, n);
    Object *pbus_mr_obj = OBJECT(pbus_mr);
    Object *parent_mr;

    if (!memory_region_is_mapped(sbdev_mr)) {
        /* Region is not mapped? */
        return -1;
    }

    parent_mr = object_property_get_link(OBJECT(sbdev_mr), "container", NULL);

    assert(parent_mr);
    if (parent_mr != pbus_mr_obj) {
        /* MMIO region is not mapped on platform bus */
        return -1;
    }

    return object_property_get_int(OBJECT(sbdev_mr), "addr", NULL);
}

static int platform_bus_count_irqs(SysBusDevice *sbdev, void *opaque)
{
    PlatformBusDevice *pbus = opaque;
    qemu_irq sbirq;
    int n, i;

    for (n = 0; ; n++) {
        if (!sysbus_has_irq(sbdev, n)) {
            break;
        }

        sbirq = sysbus_get_connected_irq(sbdev, n);
        for (i = 0; i < pbus->num_irqs; i++) {
            if (pbus->irqs[i] == sbirq) {
                bitmap_set(pbus->used_irqs, i, 1);
                break;
            }
        }
    }

    return 0;
}

/*
 * Loop through all sysbus devices and look for unassigned IRQ lines as well as
 * unassociated MMIO regions. Connect them to the platform bus if available.
 */
static void plaform_bus_refresh_irqs(PlatformBusDevice *pbus)
{
    bitmap_zero(pbus->used_irqs, pbus->num_irqs);
    foreach_dynamic_sysbus_device(platform_bus_count_irqs, pbus);
    pbus->done_gathering = true;
}

static int platform_bus_map_irq(PlatformBusDevice *pbus, SysBusDevice *sbdev,
                                int n)
{
    int max_irqs = pbus->num_irqs;
    int irqn;

    if (sysbus_is_irq_connected(sbdev, n)) {
        /* IRQ is already mapped, nothing to do */
        return 0;
    }

    irqn = find_first_zero_bit(pbus->used_irqs, max_irqs);
    if (irqn >= max_irqs) {
        hw_error("Platform Bus: Can not fit IRQ line");
        return -1;
    }

    set_bit(irqn, pbus->used_irqs);
    sysbus_connect_irq(sbdev, n, pbus->irqs[irqn]);

    return 0;
}

static int platform_bus_map_mmio(PlatformBusDevice *pbus, SysBusDevice *sbdev,
                                 int n)
{
    MemoryRegion *sbdev_mr = sysbus_mmio_get_region(sbdev, n);
    uint64_t size = memory_region_size(sbdev_mr);
    uint64_t alignment = (1ULL << (63 - clz64(size + size - 1)));
    uint64_t off;
    bool found_region = false;

    if (memory_region_is_mapped(sbdev_mr)) {
        /* Region is already mapped, nothing to do */
        return 0;
    }

    /*
     * Look for empty space in the MMIO space that is naturally aligned with
     * the target device's memory region
     */
    for (off = 0; off < pbus->mmio_size; off += alignment) {
        if (!memory_region_find(&pbus->mmio, off, size).mr) {
            found_region = true;
            break;
        }
    }

    if (!found_region) {
        hw_error("Platform Bus: Can not fit MMIO region of size %"PRIx64, size);
    }

    /* Map the device's region into our Platform Bus MMIO space */
    memory_region_add_subregion(&pbus->mmio, off, sbdev_mr);

    return 0;
}

/*
 * For each sysbus device, look for unassigned IRQ lines as well as
 * unassociated MMIO regions. Connect them to the platform bus if available.
 */
static int link_sysbus_device(SysBusDevice *sbdev, void *opaque)
{
    PlatformBusDevice *pbus = opaque;
    int i;

    for (i = 0; sysbus_has_irq(sbdev, i); i++) {
        platform_bus_map_irq(pbus, sbdev, i);
    }

    for (i = 0; sysbus_has_mmio(sbdev, i); i++) {
        platform_bus_map_mmio(pbus, sbdev, i);
    }

    return 0;
}

static void platform_bus_init_notify(Notifier *notifier, void *data)
{
    PlatformBusDevice *pb = container_of(notifier, PlatformBusDevice, notifier);

    /*
     * Generate a bitmap of used IRQ lines, as the user might have specified
     * them on the command line.
     */
    plaform_bus_refresh_irqs(pb);

    foreach_dynamic_sysbus_device(link_sysbus_device, pb);
}

static void platform_bus_realize(DeviceState *dev, Error **errp)
{
    PlatformBusDevice *pbus;
    SysBusDevice *d;
    int i;

    d = SYS_BUS_DEVICE(dev);
    pbus = PLATFORM_BUS_DEVICE(dev);

    memory_region_init(&pbus->mmio, NULL, "platform bus", pbus->mmio_size);
    sysbus_init_mmio(d, &pbus->mmio);

    pbus->used_irqs = bitmap_new(pbus->num_irqs);
    pbus->irqs = g_new0(qemu_irq, pbus->num_irqs);
    for (i = 0; i < pbus->num_irqs; i++) {
        sysbus_init_irq(d, &pbus->irqs[i]);
    }

    /*
     * Register notifier that allows us to gather dangling devices once the
     * machine is completely assembled
     */
    pbus->notifier.notify = platform_bus_init_notify;
    qemu_add_machine_init_done_notifier(&pbus->notifier);
}

static Property platform_bus_properties[] = {
    DEFINE_PROP_UINT32("num_irqs", PlatformBusDevice, num_irqs, 0),
    DEFINE_PROP_UINT32("mmio_size", PlatformBusDevice, mmio_size, 0),
    DEFINE_PROP_END_OF_LIST()
};

static void platform_bus_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = platform_bus_realize;
    dc->props = platform_bus_properties;
}

static const TypeInfo platform_bus_info = {
    .name          = TYPE_PLATFORM_BUS_DEVICE,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(PlatformBusDevice),
    .class_init    = platform_bus_class_init,
};

static void platform_bus_register_types(void)
{
    type_register_static(&platform_bus_info);
}

type_init(platform_bus_register_types)