|  | /* | 
|  | *  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.1 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 "qemu/osdep.h" | 
|  | #include "hw/platform-bus.h" | 
|  | #include "hw/qdev-properties.h" | 
|  | #include "qapi/error.h" | 
|  | #include "qemu/error-report.h" | 
|  | #include "qemu/module.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", | 
|  | &error_abort); | 
|  | if (parent_mr != pbus_mr_obj) { | 
|  | /* MMIO region is not mapped on platform bus */ | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return object_property_get_uint(OBJECT(sbdev_mr), "addr", NULL); | 
|  | } | 
|  |  | 
|  | static void 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; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * 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); | 
|  | } | 
|  |  | 
|  | static void 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; | 
|  | } | 
|  |  | 
|  | irqn = find_first_zero_bit(pbus->used_irqs, max_irqs); | 
|  | if (irqn >= max_irqs) { | 
|  | error_report("Platform Bus: Can not fit IRQ line"); | 
|  | exit(1); | 
|  | } | 
|  |  | 
|  | set_bit(irqn, pbus->used_irqs); | 
|  | sysbus_connect_irq(sbdev, n, pbus->irqs[irqn]); | 
|  | } | 
|  |  | 
|  | static void 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; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * 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) { | 
|  | MemoryRegion *mr = memory_region_find(&pbus->mmio, off, size).mr; | 
|  | if (!mr) { | 
|  | found_region = true; | 
|  | break; | 
|  | } else { | 
|  | memory_region_unref(mr); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!found_region) { | 
|  | error_report("Platform Bus: Can not fit MMIO region of size %"PRIx64, | 
|  | size); | 
|  | exit(1); | 
|  | } | 
|  |  | 
|  | /* Map the device's region into our Platform Bus MMIO space */ | 
|  | memory_region_add_subregion(&pbus->mmio, off, sbdev_mr); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Look for unassigned IRQ lines as well as unassociated MMIO regions. | 
|  | * Connect them to the platform bus if available. | 
|  | */ | 
|  | void platform_bus_link_device(PlatformBusDevice *pbus, SysBusDevice *sbdev) | 
|  | { | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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, OBJECT(dev), "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]); | 
|  | } | 
|  |  | 
|  | /* some devices might be initialized before so update used IRQs map */ | 
|  | plaform_bus_refresh_irqs(pbus); | 
|  | } | 
|  |  | 
|  | static const Property platform_bus_properties[] = { | 
|  | DEFINE_PROP_UINT32("num_irqs", PlatformBusDevice, num_irqs, 0), | 
|  | DEFINE_PROP_UINT32("mmio_size", PlatformBusDevice, mmio_size, 0), | 
|  | }; | 
|  |  | 
|  | static void platform_bus_class_init(ObjectClass *klass, void *data) | 
|  | { | 
|  | DeviceClass *dc = DEVICE_CLASS(klass); | 
|  |  | 
|  | dc->realize = platform_bus_realize; | 
|  | device_class_set_props(dc, 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) |