]>
Commit | Line | Data |
---|---|---|
11d306b9 EA |
1 | /* |
2 | * ARM Platform Bus device tree generation helpers | |
3 | * | |
4 | * Copyright (c) 2014 Linaro Limited | |
5 | * | |
6 | * Authors: | |
7 | * Alex Graf <agraf@suse.de> | |
8 | * Eric Auger <eric.auger@linaro.org> | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify it | |
11 | * under the terms and conditions of the GNU General Public License, | |
12 | * version 2 or later, as published by the Free Software Foundation. | |
13 | * | |
14 | * This program is distributed in the hope it will be useful, but WITHOUT | |
15 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
16 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
17 | * more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License along with | |
20 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
21 | * | |
22 | */ | |
23 | ||
24 | #include "hw/arm/sysbus-fdt.h" | |
25 | #include "qemu/error-report.h" | |
26 | #include "sysemu/device_tree.h" | |
27 | #include "hw/platform-bus.h" | |
28 | #include "sysemu/sysemu.h" | |
decf4f80 EA |
29 | #include "hw/vfio/vfio-platform.h" |
30 | #include "hw/vfio/vfio-calxeda-xgmac.h" | |
31 | #include "hw/arm/fdt.h" | |
11d306b9 EA |
32 | |
33 | /* | |
34 | * internal struct that contains the information to create dynamic | |
35 | * sysbus device node | |
36 | */ | |
37 | typedef struct PlatformBusFDTData { | |
38 | void *fdt; /* device tree handle */ | |
39 | int irq_start; /* index of the first IRQ usable by platform bus devices */ | |
40 | const char *pbus_node_name; /* name of the platform bus node */ | |
41 | PlatformBusDevice *pbus; | |
42 | } PlatformBusFDTData; | |
43 | ||
44 | /* | |
45 | * struct used when calling the machine init done notifier | |
46 | * that constructs the fdt nodes of platform bus devices | |
47 | */ | |
48 | typedef struct PlatformBusFDTNotifierParams { | |
49 | Notifier notifier; | |
50 | ARMPlatformBusFDTParams *fdt_params; | |
51 | } PlatformBusFDTNotifierParams; | |
52 | ||
53 | /* struct that associates a device type name and a node creation function */ | |
54 | typedef struct NodeCreationPair { | |
55 | const char *typename; | |
56 | int (*add_fdt_node_fn)(SysBusDevice *sbdev, void *opaque); | |
57 | } NodeCreationPair; | |
58 | ||
decf4f80 EA |
59 | /* Device Specific Code */ |
60 | ||
61 | /** | |
62 | * add_calxeda_midway_xgmac_fdt_node | |
63 | * | |
64 | * Generates a simple node with following properties: | |
65 | * compatible string, regs, interrupts, dma-coherent | |
66 | */ | |
67 | static int add_calxeda_midway_xgmac_fdt_node(SysBusDevice *sbdev, void *opaque) | |
68 | { | |
69 | PlatformBusFDTData *data = opaque; | |
70 | PlatformBusDevice *pbus = data->pbus; | |
71 | void *fdt = data->fdt; | |
72 | const char *parent_node = data->pbus_node_name; | |
73 | int compat_str_len, i, ret = -1; | |
74 | char *nodename; | |
75 | uint32_t *irq_attr, *reg_attr; | |
76 | uint64_t mmio_base, irq_number; | |
77 | VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(sbdev); | |
78 | VFIODevice *vbasedev = &vdev->vbasedev; | |
79 | ||
80 | mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, 0); | |
81 | nodename = g_strdup_printf("%s/%s@%" PRIx64, parent_node, | |
82 | vbasedev->name, mmio_base); | |
83 | qemu_fdt_add_subnode(fdt, nodename); | |
84 | ||
85 | compat_str_len = strlen(vdev->compat) + 1; | |
86 | qemu_fdt_setprop(fdt, nodename, "compatible", | |
87 | vdev->compat, compat_str_len); | |
88 | ||
89 | qemu_fdt_setprop(fdt, nodename, "dma-coherent", "", 0); | |
90 | ||
91 | reg_attr = g_new(uint32_t, vbasedev->num_regions * 2); | |
92 | for (i = 0; i < vbasedev->num_regions; i++) { | |
93 | mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, i); | |
94 | reg_attr[2 * i] = cpu_to_be32(mmio_base); | |
95 | reg_attr[2 * i + 1] = cpu_to_be32( | |
96 | memory_region_size(&vdev->regions[i]->mem)); | |
97 | } | |
98 | ret = qemu_fdt_setprop(fdt, nodename, "reg", reg_attr, | |
99 | vbasedev->num_regions * 2 * sizeof(uint32_t)); | |
100 | if (ret) { | |
101 | error_report("could not set reg property of node %s", nodename); | |
102 | goto fail_reg; | |
103 | } | |
104 | ||
105 | irq_attr = g_new(uint32_t, vbasedev->num_irqs * 3); | |
106 | for (i = 0; i < vbasedev->num_irqs; i++) { | |
107 | irq_number = platform_bus_get_irqn(pbus, sbdev , i) | |
108 | + data->irq_start; | |
109 | irq_attr[3 * i] = cpu_to_be32(GIC_FDT_IRQ_TYPE_SPI); | |
110 | irq_attr[3 * i + 1] = cpu_to_be32(irq_number); | |
111 | irq_attr[3 * i + 2] = cpu_to_be32(GIC_FDT_IRQ_FLAGS_LEVEL_HI); | |
112 | } | |
113 | ret = qemu_fdt_setprop(fdt, nodename, "interrupts", | |
114 | irq_attr, vbasedev->num_irqs * 3 * sizeof(uint32_t)); | |
115 | if (ret) { | |
116 | error_report("could not set interrupts property of node %s", | |
117 | nodename); | |
118 | } | |
119 | g_free(irq_attr); | |
120 | fail_reg: | |
121 | g_free(reg_attr); | |
122 | g_free(nodename); | |
123 | return ret; | |
124 | } | |
125 | ||
11d306b9 EA |
126 | /* list of supported dynamic sysbus devices */ |
127 | static const NodeCreationPair add_fdt_node_functions[] = { | |
decf4f80 | 128 | {TYPE_VFIO_CALXEDA_XGMAC, add_calxeda_midway_xgmac_fdt_node}, |
11d306b9 EA |
129 | {"", NULL}, /* last element */ |
130 | }; | |
131 | ||
decf4f80 EA |
132 | /* Generic Code */ |
133 | ||
11d306b9 EA |
134 | /** |
135 | * add_fdt_node - add the device tree node of a dynamic sysbus device | |
136 | * | |
137 | * @sbdev: handle to the sysbus device | |
138 | * @opaque: handle to the PlatformBusFDTData | |
139 | * | |
140 | * Checks the sysbus type belongs to the list of device types that | |
141 | * are dynamically instantiable and if so call the node creation | |
142 | * function. | |
143 | */ | |
144 | static int add_fdt_node(SysBusDevice *sbdev, void *opaque) | |
145 | { | |
146 | int i, ret; | |
147 | ||
148 | for (i = 0; i < ARRAY_SIZE(add_fdt_node_functions); i++) { | |
149 | if (!strcmp(object_get_typename(OBJECT(sbdev)), | |
150 | add_fdt_node_functions[i].typename)) { | |
151 | ret = add_fdt_node_functions[i].add_fdt_node_fn(sbdev, opaque); | |
152 | assert(!ret); | |
153 | return 0; | |
154 | } | |
155 | } | |
156 | error_report("Device %s can not be dynamically instantiated", | |
157 | qdev_fw_name(DEVICE(sbdev))); | |
158 | exit(1); | |
159 | } | |
160 | ||
161 | /** | |
162 | * add_all_platform_bus_fdt_nodes - create all the platform bus nodes | |
163 | * | |
164 | * builds the parent platform bus node and all the nodes of dynamic | |
165 | * sysbus devices attached to it. | |
166 | */ | |
167 | static void add_all_platform_bus_fdt_nodes(ARMPlatformBusFDTParams *fdt_params) | |
168 | { | |
169 | const char platcomp[] = "qemu,platform\0simple-bus"; | |
170 | PlatformBusDevice *pbus; | |
171 | DeviceState *dev; | |
172 | gchar *node; | |
173 | uint64_t addr, size; | |
174 | int irq_start, dtb_size; | |
175 | struct arm_boot_info *info = fdt_params->binfo; | |
176 | const ARMPlatformBusSystemParams *params = fdt_params->system_params; | |
177 | const char *intc = fdt_params->intc; | |
178 | void *fdt = info->get_dtb(info, &dtb_size); | |
179 | ||
180 | /* | |
181 | * If the user provided a dtb, we assume the dynamic sysbus nodes | |
182 | * already are integrated there. This corresponds to a use case where | |
183 | * the dynamic sysbus nodes are complex and their generation is not yet | |
184 | * supported. In that case the user can take charge of the guest dt | |
185 | * while qemu takes charge of the qom stuff. | |
186 | */ | |
187 | if (info->dtb_filename) { | |
188 | return; | |
189 | } | |
190 | ||
191 | assert(fdt); | |
192 | ||
193 | node = g_strdup_printf("/platform@%"PRIx64, params->platform_bus_base); | |
194 | addr = params->platform_bus_base; | |
195 | size = params->platform_bus_size; | |
196 | irq_start = params->platform_bus_first_irq; | |
197 | ||
198 | /* Create a /platform node that we can put all devices into */ | |
199 | qemu_fdt_add_subnode(fdt, node); | |
200 | qemu_fdt_setprop(fdt, node, "compatible", platcomp, sizeof(platcomp)); | |
201 | ||
202 | /* Our platform bus region is less than 32bits, so 1 cell is enough for | |
203 | * address and size | |
204 | */ | |
205 | qemu_fdt_setprop_cells(fdt, node, "#size-cells", 1); | |
206 | qemu_fdt_setprop_cells(fdt, node, "#address-cells", 1); | |
207 | qemu_fdt_setprop_cells(fdt, node, "ranges", 0, addr >> 32, addr, size); | |
208 | ||
209 | qemu_fdt_setprop_phandle(fdt, node, "interrupt-parent", intc); | |
210 | ||
211 | dev = qdev_find_recursive(sysbus_get_default(), TYPE_PLATFORM_BUS_DEVICE); | |
212 | pbus = PLATFORM_BUS_DEVICE(dev); | |
213 | ||
214 | /* We can only create dt nodes for dynamic devices when they're ready */ | |
215 | assert(pbus->done_gathering); | |
216 | ||
217 | PlatformBusFDTData data = { | |
218 | .fdt = fdt, | |
219 | .irq_start = irq_start, | |
220 | .pbus_node_name = node, | |
221 | .pbus = pbus, | |
222 | }; | |
223 | ||
224 | /* Loop through all dynamic sysbus devices and create their node */ | |
225 | foreach_dynamic_sysbus_device(add_fdt_node, &data); | |
226 | ||
227 | g_free(node); | |
228 | } | |
229 | ||
230 | static void platform_bus_fdt_notify(Notifier *notifier, void *data) | |
231 | { | |
232 | PlatformBusFDTNotifierParams *p = DO_UPCAST(PlatformBusFDTNotifierParams, | |
233 | notifier, notifier); | |
234 | ||
235 | add_all_platform_bus_fdt_nodes(p->fdt_params); | |
236 | g_free(p->fdt_params); | |
237 | g_free(p); | |
238 | } | |
239 | ||
240 | void arm_register_platform_bus_fdt_creator(ARMPlatformBusFDTParams *fdt_params) | |
241 | { | |
242 | PlatformBusFDTNotifierParams *p = g_new(PlatformBusFDTNotifierParams, 1); | |
243 | ||
244 | p->fdt_params = fdt_params; | |
245 | p->notifier.notify = platform_bus_fdt_notify; | |
246 | qemu_add_machine_init_done_notifier(&p->notifier); | |
247 | } |