qemu/hw/arm/sysbus-fdt.c
<<
>>
Prefs
   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 "qemu/osdep.h"
  25#include "qapi/error.h"
  26#include <libfdt.h>
  27#include "qemu-common.h"
  28#ifdef CONFIG_LINUX
  29#include <linux/vfio.h>
  30#endif
  31#include "hw/arm/sysbus-fdt.h"
  32#include "qemu/error-report.h"
  33#include "sysemu/device_tree.h"
  34#include "hw/platform-bus.h"
  35#include "sysemu/sysemu.h"
  36#include "hw/vfio/vfio-platform.h"
  37#include "hw/vfio/vfio-calxeda-xgmac.h"
  38#include "hw/vfio/vfio-amd-xgbe.h"
  39#include "hw/arm/fdt.h"
  40
  41/*
  42 * internal struct that contains the information to create dynamic
  43 * sysbus device node
  44 */
  45typedef struct PlatformBusFDTData {
  46    void *fdt; /* device tree handle */
  47    int irq_start; /* index of the first IRQ usable by platform bus devices */
  48    const char *pbus_node_name; /* name of the platform bus node */
  49    PlatformBusDevice *pbus;
  50} PlatformBusFDTData;
  51
  52/*
  53 * struct used when calling the machine init done notifier
  54 * that constructs the fdt nodes of platform bus devices
  55 */
  56typedef struct PlatformBusFDTNotifierParams {
  57    Notifier notifier;
  58    ARMPlatformBusFDTParams *fdt_params;
  59} PlatformBusFDTNotifierParams;
  60
  61/* struct that associates a device type name and a node creation function */
  62typedef struct NodeCreationPair {
  63    const char *typename;
  64    int (*add_fdt_node_fn)(SysBusDevice *sbdev, void *opaque);
  65} NodeCreationPair;
  66
  67/* helpers */
  68
  69typedef struct HostProperty {
  70    const char *name;
  71    bool optional;
  72} HostProperty;
  73
  74#ifdef CONFIG_LINUX
  75
  76/**
  77 * copy_properties_from_host
  78 *
  79 * copies properties listed in an array from host device tree to
  80 * guest device tree. If a non optional property is not found, the
  81 * function asserts. An optional property is ignored if not found
  82 * in the host device tree.
  83 * @props: array of HostProperty to copy
  84 * @nb_props: number of properties in the array
  85 * @host_dt: host device tree blob
  86 * @guest_dt: guest device tree blob
  87 * @node_path: host dt node path where the property is supposed to be
  88              found
  89 * @nodename: guest node name the properties should be added to
  90 */
  91static void copy_properties_from_host(HostProperty *props, int nb_props,
  92                                      void *host_fdt, void *guest_fdt,
  93                                      char *node_path, char *nodename)
  94{
  95    int i, prop_len;
  96    const void *r;
  97    Error *err = NULL;
  98
  99    for (i = 0; i < nb_props; i++) {
 100        r = qemu_fdt_getprop(host_fdt, node_path,
 101                             props[i].name,
 102                             &prop_len, 0,
 103                             props[i].optional ? &err : &error_fatal);
 104        if (r) {
 105            qemu_fdt_setprop(guest_fdt, nodename,
 106                             props[i].name, r, prop_len);
 107        } else {
 108            if (prop_len != -FDT_ERR_NOTFOUND) {
 109                /* optional property not returned although property exists */
 110                error_report_err(err);
 111            } else {
 112                error_free(err);
 113            }
 114        }
 115    }
 116}
 117
 118/* clock properties whose values are copied/pasted from host */
 119static HostProperty clock_copied_properties[] = {
 120    {"compatible", false},
 121    {"#clock-cells", false},
 122    {"clock-frequency", true},
 123    {"clock-output-names", true},
 124};
 125
 126/**
 127 * fdt_build_clock_node
 128 *
 129 * Build a guest clock node, used as a dependency from a passthrough'ed
 130 * device. Most information are retrieved from the host clock node.
 131 * Also check the host clock is a fixed one.
 132 *
 133 * @host_fdt: host device tree blob from which info are retrieved
 134 * @guest_fdt: guest device tree blob where the clock node is added
 135 * @host_phandle: phandle of the clock in host device tree
 136 * @guest_phandle: phandle to assign to the guest node
 137 */
 138static void fdt_build_clock_node(void *host_fdt, void *guest_fdt,
 139                                uint32_t host_phandle,
 140                                uint32_t guest_phandle)
 141{
 142    char *node_path = NULL;
 143    char *nodename;
 144    const void *r;
 145    int ret, node_offset, prop_len, path_len = 16;
 146
 147    node_offset = fdt_node_offset_by_phandle(host_fdt, host_phandle);
 148    if (node_offset <= 0) {
 149        error_setg(&error_fatal,
 150                   "not able to locate clock handle %d in host device tree",
 151                   host_phandle);
 152    }
 153    node_path = g_malloc(path_len);
 154    while ((ret = fdt_get_path(host_fdt, node_offset, node_path, path_len))
 155            == -FDT_ERR_NOSPACE) {
 156        path_len += 16;
 157        node_path = g_realloc(node_path, path_len);
 158    }
 159    if (ret < 0) {
 160        error_setg(&error_fatal,
 161                   "not able to retrieve node path for clock handle %d",
 162                   host_phandle);
 163    }
 164
 165    r = qemu_fdt_getprop(host_fdt, node_path, "compatible", &prop_len,
 166                         0, &error_fatal);
 167    if (strcmp(r, "fixed-clock")) {
 168        error_setg(&error_fatal,
 169                   "clock handle %d is not a fixed clock", host_phandle);
 170    }
 171
 172    nodename = strrchr(node_path, '/');
 173    qemu_fdt_add_subnode(guest_fdt, nodename);
 174
 175    copy_properties_from_host(clock_copied_properties,
 176                              ARRAY_SIZE(clock_copied_properties),
 177                              host_fdt, guest_fdt,
 178                              node_path, nodename);
 179
 180    qemu_fdt_setprop_cell(guest_fdt, nodename, "phandle", guest_phandle);
 181
 182    g_free(node_path);
 183}
 184
 185/**
 186 * sysfs_to_dt_name: convert the name found in sysfs into the node name
 187 * for instance e0900000.xgmac is converted into xgmac@e0900000
 188 * @sysfs_name: directory name in sysfs
 189 *
 190 * returns the device tree name upon success or NULL in case the sysfs name
 191 * does not match the expected format
 192 */
 193static char *sysfs_to_dt_name(const char *sysfs_name)
 194{
 195    gchar **substrings =  g_strsplit(sysfs_name, ".", 2);
 196    char *dt_name = NULL;
 197
 198    if (!substrings || !substrings[0] || !substrings[1]) {
 199        goto out;
 200    }
 201    dt_name = g_strdup_printf("%s@%s", substrings[1], substrings[0]);
 202out:
 203    g_strfreev(substrings);
 204    return dt_name;
 205}
 206
 207/* Device Specific Code */
 208
 209/**
 210 * add_calxeda_midway_xgmac_fdt_node
 211 *
 212 * Generates a simple node with following properties:
 213 * compatible string, regs, interrupts, dma-coherent
 214 */
 215static int add_calxeda_midway_xgmac_fdt_node(SysBusDevice *sbdev, void *opaque)
 216{
 217    PlatformBusFDTData *data = opaque;
 218    PlatformBusDevice *pbus = data->pbus;
 219    void *fdt = data->fdt;
 220    const char *parent_node = data->pbus_node_name;
 221    int compat_str_len, i;
 222    char *nodename;
 223    uint32_t *irq_attr, *reg_attr;
 224    uint64_t mmio_base, irq_number;
 225    VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(sbdev);
 226    VFIODevice *vbasedev = &vdev->vbasedev;
 227
 228    mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, 0);
 229    nodename = g_strdup_printf("%s/%s@%" PRIx64, parent_node,
 230                               vbasedev->name, mmio_base);
 231    qemu_fdt_add_subnode(fdt, nodename);
 232
 233    compat_str_len = strlen(vdev->compat) + 1;
 234    qemu_fdt_setprop(fdt, nodename, "compatible",
 235                          vdev->compat, compat_str_len);
 236
 237    qemu_fdt_setprop(fdt, nodename, "dma-coherent", "", 0);
 238
 239    reg_attr = g_new(uint32_t, vbasedev->num_regions * 2);
 240    for (i = 0; i < vbasedev->num_regions; i++) {
 241        mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, i);
 242        reg_attr[2 * i] = cpu_to_be32(mmio_base);
 243        reg_attr[2 * i + 1] = cpu_to_be32(
 244                                memory_region_size(vdev->regions[i]->mem));
 245    }
 246    qemu_fdt_setprop(fdt, nodename, "reg", reg_attr,
 247                     vbasedev->num_regions * 2 * sizeof(uint32_t));
 248
 249    irq_attr = g_new(uint32_t, vbasedev->num_irqs * 3);
 250    for (i = 0; i < vbasedev->num_irqs; i++) {
 251        irq_number = platform_bus_get_irqn(pbus, sbdev , i)
 252                         + data->irq_start;
 253        irq_attr[3 * i] = cpu_to_be32(GIC_FDT_IRQ_TYPE_SPI);
 254        irq_attr[3 * i + 1] = cpu_to_be32(irq_number);
 255        irq_attr[3 * i + 2] = cpu_to_be32(GIC_FDT_IRQ_FLAGS_LEVEL_HI);
 256    }
 257    qemu_fdt_setprop(fdt, nodename, "interrupts",
 258                     irq_attr, vbasedev->num_irqs * 3 * sizeof(uint32_t));
 259    g_free(irq_attr);
 260    g_free(reg_attr);
 261    g_free(nodename);
 262    return 0;
 263}
 264
 265/* AMD xgbe properties whose values are copied/pasted from host */
 266static HostProperty amd_xgbe_copied_properties[] = {
 267    {"compatible", false},
 268    {"dma-coherent", true},
 269    {"amd,per-channel-interrupt", true},
 270    {"phy-mode", false},
 271    {"mac-address", true},
 272    {"amd,speed-set", false},
 273    {"amd,serdes-blwc", true},
 274    {"amd,serdes-cdr-rate", true},
 275    {"amd,serdes-pq-skew", true},
 276    {"amd,serdes-tx-amp", true},
 277    {"amd,serdes-dfe-tap-config", true},
 278    {"amd,serdes-dfe-tap-enable", true},
 279    {"clock-names", false},
 280};
 281
 282/**
 283 * add_amd_xgbe_fdt_node
 284 *
 285 * Generates the combined xgbe/phy node following kernel >=4.2
 286 * binding documentation:
 287 * Documentation/devicetree/bindings/net/amd-xgbe.txt:
 288 * Also 2 clock nodes are created (dma and ptp)
 289 *
 290 * Asserts in case of error
 291 */
 292static int add_amd_xgbe_fdt_node(SysBusDevice *sbdev, void *opaque)
 293{
 294    PlatformBusFDTData *data = opaque;
 295    PlatformBusDevice *pbus = data->pbus;
 296    VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(sbdev);
 297    VFIODevice *vbasedev = &vdev->vbasedev;
 298    VFIOINTp *intp;
 299    const char *parent_node = data->pbus_node_name;
 300    char **node_path, *nodename, *dt_name;
 301    void *guest_fdt = data->fdt, *host_fdt;
 302    const void *r;
 303    int i, prop_len;
 304    uint32_t *irq_attr, *reg_attr, *host_clock_phandles;
 305    uint64_t mmio_base, irq_number;
 306    uint32_t guest_clock_phandles[2];
 307
 308    host_fdt = load_device_tree_from_sysfs();
 309
 310    dt_name = sysfs_to_dt_name(vbasedev->name);
 311    if (!dt_name) {
 312        error_setg(&error_fatal, "%s incorrect sysfs device name %s",
 313                    __func__, vbasedev->name);
 314    }
 315    node_path = qemu_fdt_node_path(host_fdt, dt_name, vdev->compat,
 316                                   &error_fatal);
 317    if (!node_path || !node_path[0]) {
 318        error_setg(&error_fatal, "%s unable to retrieve node path for %s/%s",
 319                   __func__, dt_name, vdev->compat);
 320    }
 321
 322    if (node_path[1]) {
 323        error_setg(&error_fatal, "%s more than one node matching %s/%s!",
 324                   __func__, dt_name, vdev->compat);
 325    }
 326
 327    g_free(dt_name);
 328
 329    if (vbasedev->num_regions != 5) {
 330        error_setg(&error_fatal, "%s Does the host dt node combine XGBE/PHY?",
 331                   __func__);
 332    }
 333
 334    /* generate nodes for DMA_CLK and PTP_CLK */
 335    r = qemu_fdt_getprop(host_fdt, node_path[0], "clocks",
 336                         &prop_len, 0, &error_fatal);
 337    if (prop_len != 8) {
 338        error_setg(&error_fatal, "%s clocks property should contain 2 handles",
 339                   __func__);
 340    }
 341    host_clock_phandles = (uint32_t *)r;
 342    guest_clock_phandles[0] = qemu_fdt_alloc_phandle(guest_fdt);
 343    guest_clock_phandles[1] = qemu_fdt_alloc_phandle(guest_fdt);
 344
 345    /**
 346     * clock handles fetched from host dt are in be32 layout whereas
 347     * rest of the code uses cpu layout. Also guest clock handles are
 348     * in cpu layout.
 349     */
 350    fdt_build_clock_node(host_fdt, guest_fdt,
 351                         be32_to_cpu(host_clock_phandles[0]),
 352                         guest_clock_phandles[0]);
 353
 354    fdt_build_clock_node(host_fdt, guest_fdt,
 355                         be32_to_cpu(host_clock_phandles[1]),
 356                         guest_clock_phandles[1]);
 357
 358    /* combined XGBE/PHY node */
 359    mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, 0);
 360    nodename = g_strdup_printf("%s/%s@%" PRIx64, parent_node,
 361                               vbasedev->name, mmio_base);
 362    qemu_fdt_add_subnode(guest_fdt, nodename);
 363
 364    copy_properties_from_host(amd_xgbe_copied_properties,
 365                       ARRAY_SIZE(amd_xgbe_copied_properties),
 366                       host_fdt, guest_fdt,
 367                       node_path[0], nodename);
 368
 369    qemu_fdt_setprop_cells(guest_fdt, nodename, "clocks",
 370                           guest_clock_phandles[0],
 371                           guest_clock_phandles[1]);
 372
 373    reg_attr = g_new(uint32_t, vbasedev->num_regions * 2);
 374    for (i = 0; i < vbasedev->num_regions; i++) {
 375        mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, i);
 376        reg_attr[2 * i] = cpu_to_be32(mmio_base);
 377        reg_attr[2 * i + 1] = cpu_to_be32(
 378                                memory_region_size(vdev->regions[i]->mem));
 379    }
 380    qemu_fdt_setprop(guest_fdt, nodename, "reg", reg_attr,
 381                     vbasedev->num_regions * 2 * sizeof(uint32_t));
 382
 383    irq_attr = g_new(uint32_t, vbasedev->num_irqs * 3);
 384    for (i = 0; i < vbasedev->num_irqs; i++) {
 385        irq_number = platform_bus_get_irqn(pbus, sbdev , i)
 386                         + data->irq_start;
 387        irq_attr[3 * i] = cpu_to_be32(GIC_FDT_IRQ_TYPE_SPI);
 388        irq_attr[3 * i + 1] = cpu_to_be32(irq_number);
 389        /*
 390          * General device interrupt and PCS auto-negotiation interrupts are
 391          * level-sensitive while the 4 per-channel interrupts are edge
 392          * sensitive
 393          */
 394        QLIST_FOREACH(intp, &vdev->intp_list, next) {
 395            if (intp->pin == i) {
 396                break;
 397            }
 398        }
 399        if (intp->flags & VFIO_IRQ_INFO_AUTOMASKED) {
 400            irq_attr[3 * i + 2] = cpu_to_be32(GIC_FDT_IRQ_FLAGS_LEVEL_HI);
 401        } else {
 402            irq_attr[3 * i + 2] = cpu_to_be32(GIC_FDT_IRQ_FLAGS_EDGE_LO_HI);
 403        }
 404    }
 405    qemu_fdt_setprop(guest_fdt, nodename, "interrupts",
 406                     irq_attr, vbasedev->num_irqs * 3 * sizeof(uint32_t));
 407
 408    g_free(host_fdt);
 409    g_strfreev(node_path);
 410    g_free(irq_attr);
 411    g_free(reg_attr);
 412    g_free(nodename);
 413    return 0;
 414}
 415
 416#endif /* CONFIG_LINUX */
 417
 418/* list of supported dynamic sysbus devices */
 419static const NodeCreationPair add_fdt_node_functions[] = {
 420#ifdef CONFIG_LINUX
 421    {TYPE_VFIO_CALXEDA_XGMAC, add_calxeda_midway_xgmac_fdt_node},
 422    {TYPE_VFIO_AMD_XGBE, add_amd_xgbe_fdt_node},
 423#endif
 424    {"", NULL}, /* last element */
 425};
 426
 427/* Generic Code */
 428
 429/**
 430 * add_fdt_node - add the device tree node of a dynamic sysbus device
 431 *
 432 * @sbdev: handle to the sysbus device
 433 * @opaque: handle to the PlatformBusFDTData
 434 *
 435 * Checks the sysbus type belongs to the list of device types that
 436 * are dynamically instantiable and if so call the node creation
 437 * function.
 438 */
 439static void add_fdt_node(SysBusDevice *sbdev, void *opaque)
 440{
 441    int i, ret;
 442
 443    for (i = 0; i < ARRAY_SIZE(add_fdt_node_functions); i++) {
 444        if (!strcmp(object_get_typename(OBJECT(sbdev)),
 445                    add_fdt_node_functions[i].typename)) {
 446            ret = add_fdt_node_functions[i].add_fdt_node_fn(sbdev, opaque);
 447            assert(!ret);
 448            return;
 449        }
 450    }
 451    error_report("Device %s can not be dynamically instantiated",
 452                     qdev_fw_name(DEVICE(sbdev)));
 453    exit(1);
 454}
 455
 456/**
 457 * add_all_platform_bus_fdt_nodes - create all the platform bus nodes
 458 *
 459 * builds the parent platform bus node and all the nodes of dynamic
 460 * sysbus devices attached to it.
 461 */
 462static void add_all_platform_bus_fdt_nodes(ARMPlatformBusFDTParams *fdt_params)
 463{
 464    const char platcomp[] = "qemu,platform\0simple-bus";
 465    PlatformBusDevice *pbus;
 466    DeviceState *dev;
 467    gchar *node;
 468    uint64_t addr, size;
 469    int irq_start, dtb_size;
 470    struct arm_boot_info *info = fdt_params->binfo;
 471    const ARMPlatformBusSystemParams *params = fdt_params->system_params;
 472    const char *intc = fdt_params->intc;
 473    void *fdt = info->get_dtb(info, &dtb_size);
 474
 475    /*
 476     * If the user provided a dtb, we assume the dynamic sysbus nodes
 477     * already are integrated there. This corresponds to a use case where
 478     * the dynamic sysbus nodes are complex and their generation is not yet
 479     * supported. In that case the user can take charge of the guest dt
 480     * while qemu takes charge of the qom stuff.
 481     */
 482    if (info->dtb_filename) {
 483        return;
 484    }
 485
 486    assert(fdt);
 487
 488    node = g_strdup_printf("/platform@%"PRIx64, params->platform_bus_base);
 489    addr = params->platform_bus_base;
 490    size = params->platform_bus_size;
 491    irq_start = params->platform_bus_first_irq;
 492
 493    /* Create a /platform node that we can put all devices into */
 494    qemu_fdt_add_subnode(fdt, node);
 495    qemu_fdt_setprop(fdt, node, "compatible", platcomp, sizeof(platcomp));
 496
 497    /* Our platform bus region is less than 32bits, so 1 cell is enough for
 498     * address and size
 499     */
 500    qemu_fdt_setprop_cells(fdt, node, "#size-cells", 1);
 501    qemu_fdt_setprop_cells(fdt, node, "#address-cells", 1);
 502    qemu_fdt_setprop_cells(fdt, node, "ranges", 0, addr >> 32, addr, size);
 503
 504    qemu_fdt_setprop_phandle(fdt, node, "interrupt-parent", intc);
 505
 506    dev = qdev_find_recursive(sysbus_get_default(), TYPE_PLATFORM_BUS_DEVICE);
 507    pbus = PLATFORM_BUS_DEVICE(dev);
 508
 509    /* We can only create dt nodes for dynamic devices when they're ready */
 510    assert(pbus->done_gathering);
 511
 512    PlatformBusFDTData data = {
 513        .fdt = fdt,
 514        .irq_start = irq_start,
 515        .pbus_node_name = node,
 516        .pbus = pbus,
 517    };
 518
 519    /* Loop through all dynamic sysbus devices and create their node */
 520    foreach_dynamic_sysbus_device(add_fdt_node, &data);
 521
 522    g_free(node);
 523}
 524
 525static void platform_bus_fdt_notify(Notifier *notifier, void *data)
 526{
 527    PlatformBusFDTNotifierParams *p = DO_UPCAST(PlatformBusFDTNotifierParams,
 528                                                notifier, notifier);
 529
 530    add_all_platform_bus_fdt_nodes(p->fdt_params);
 531    g_free(p->fdt_params);
 532    g_free(p);
 533}
 534
 535void arm_register_platform_bus_fdt_creator(ARMPlatformBusFDTParams *fdt_params)
 536{
 537    PlatformBusFDTNotifierParams *p = g_new(PlatformBusFDTNotifierParams, 1);
 538
 539    p->fdt_params = fdt_params;
 540    p->notifier.notify = platform_bus_fdt_notify;
 541    qemu_add_machine_init_done_notifier(&p->notifier);
 542}
 543