root/sys/dev/virtio/arch/x86/virtio_mmio_cmdline.c
/* $NetBSD: virtio_mmio_cmdline.c,v 1.1 2025/01/15 13:16:23 imil Exp $ */

/*-
 * Copyright (c) 2025 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Emile 'iMil' Heitor.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*-
 * Copyright (c) 2022 Colin Percival
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>

#define VIRTIO_PRIVATE
#include <dev/virtio/virtio_mmiovar.h>
#include <arch/x86/pv/pvvar.h>
#include <xen/hypervisor.h>

#include <machine/i82093var.h>
#include "ioapic.h"

#define VMMIOSTR "virtio_mmio.device="

struct mmio_args {
        uint64_t        sz;
        uint64_t        baseaddr;
        uint64_t        irq;
        uint64_t        id;
};

struct virtio_mmio_cmdline_softc {
        struct virtio_mmio_softc        sc_msc;
        struct mmio_args                margs;
};

static int      virtio_mmio_cmdline_match(device_t, cfdata_t, void *);
static void     virtio_mmio_cmdline_attach(device_t, device_t, void *);
static int      virtio_mmio_cmdline_do_attach(device_t,
                    struct pv_attach_args *, struct mmio_args *);
static int      virtio_mmio_cmdline_detach(device_t, int);
static int      virtio_mmio_cmdline_rescan(device_t, const char *, const int *);
static int      virtio_mmio_cmdline_alloc_interrupts(struct virtio_mmio_softc *);
static void     virtio_mmio_cmdline_free_interrupts(struct virtio_mmio_softc *);

CFATTACH_DECL3_NEW(mmio_cmdline,
    sizeof(struct virtio_mmio_cmdline_softc),
    virtio_mmio_cmdline_match, virtio_mmio_cmdline_attach,
    virtio_mmio_cmdline_detach, NULL,
    virtio_mmio_cmdline_rescan, NULL, 0);

static int
virtio_mmio_cmdline_match(device_t parent, cfdata_t match, void *aux)
{
        if (strstr(xen_start_info.cmd_line, VMMIOSTR) == NULL)
                return 0;

        return 1;
}

static void
parsearg(struct mmio_args *margs, const char *arg)
{
        char *p;

        /* <size> */
        margs->sz = strtoull(arg, (char **)&p, 0);
        if ((margs->sz == 0) || (margs->sz == UINT64_MAX))
                goto bad;
        switch (*p) {
        case 'E': case 'e':
                /* Check for overflow */
                if (margs->sz > (UINT64_MAX >> 60))
                        goto bad;
                margs->sz <<= 10;
                /* FALLTHROUGH */
        case 'P': case 'p':
                if (margs->sz > (UINT64_MAX >> 50))
                        goto bad;
                margs->sz <<= 10;
                /* FALLTHROUGH */
        case 'T': case 't':
                if (margs->sz > (UINT64_MAX >> 40))
                        goto bad;
                margs->sz <<= 10;
                /* FALLTHROUGH */
        case 'G': case 'g':
                if (margs->sz > (UINT64_MAX >> 30))
                        goto bad;
                margs->sz <<= 10;
                /* FALLTHROUGH */
        case 'M': case 'm':
                if (margs->sz > (UINT64_MAX >> 20))
                        goto bad;
                margs->sz <<= 10;
                /* FALLTHROUGH */
        case 'K': case 'k':
                if (margs->sz > (UINT64_MAX >> 10))
                        goto bad;
                margs->sz <<= 10;
                p++;
                break;
        }

        /* @<baseaddr> */
        if (*p++ != '@')
                goto bad;
        margs->baseaddr = strtoull(p, (char **)&p, 0);
        if ((margs->baseaddr == 0) || (margs->baseaddr == UINT64_MAX))
                goto bad;

        /* :<irq> */
        if (*p++ != ':')
                goto bad;
        margs->irq = strtoull(p, (char **)&p, 0);
        if ((margs->irq == 0) || (margs->irq == UINT64_MAX))
                goto bad;

        /* Optionally, :<id> */
        if (*p) {
                if (*p++ != ':')
                        goto bad;
                margs->id = strtoull(p, (char **)&p, 0);
                if ((margs->id == 0) || (margs->id == UINT64_MAX))
                        goto bad;
        } else {
                margs->id = 0;
        }

        /* Should have reached the end of the string. */
        if (*p)
                goto bad;

        return;

bad:
        aprint_error("Error parsing virtio_mmio parameter: %s\n", arg);
}

static void
virtio_mmio_cmdline_attach(device_t parent, device_t self, void *aux)
{
        struct virtio_mmio_cmdline_softc *sc = device_private(self);
        struct pv_attach_args *pvaa = aux;
        struct mmio_args *margs = &sc->margs;
        int keylen = strlen(VMMIOSTR);
        char *next;
        static char cmdline[LINE_MAX], *parg = NULL;

        aprint_normal("\n");
        aprint_naive("\n");

        if (parg == NULL) { /* first pass */
                strlcpy(cmdline, xen_start_info.cmd_line, sizeof(cmdline));
                aprint_verbose_dev(self, "kernel parameters: %s\n",
                    cmdline);
                parg = strstr(cmdline, VMMIOSTR);
        }

        if (parg != NULL) {
                parg += keylen;
                if (!*parg)
                        return;

                next = parg;
                while (*next && *next != ' ') /* find end of argument */
                        next++;
                if (*next) { /* space */
                        *next++ = '\0'; /* end the argument string */
                        next = strstr(next, VMMIOSTR);
                }

                aprint_normal_dev(self, "viommio: %s\n", parg);
                parsearg(margs, parg);

                if (virtio_mmio_cmdline_do_attach(self, pvaa, margs))
                        return;

                if (next) {
                        parg = next;
                        config_found(parent, pvaa, NULL, CFARGS_NONE);
                }
        }
}

static int
virtio_mmio_cmdline_do_attach(device_t self,
    struct pv_attach_args *pvaa,
    struct mmio_args *margs)
{
        struct virtio_mmio_cmdline_softc *sc = device_private(self);
        struct virtio_mmio_softc *const msc = &sc->sc_msc;
        struct virtio_softc *const vsc = &msc->sc_sc;
        int error;

        msc->sc_iot = pvaa->pvaa_memt;
        vsc->sc_dmat = pvaa->pvaa_dmat;
        msc->sc_iosize = margs->sz;
        vsc->sc_dev = self;

        error = bus_space_map(msc->sc_iot, margs->baseaddr, margs->sz, 0,
            &msc->sc_ioh);
        if (error) {
                aprint_error_dev(self, "couldn't map %#" PRIx64 ": %d",
                    margs->baseaddr, error);
                return error;
        }

        msc->sc_alloc_interrupts = virtio_mmio_cmdline_alloc_interrupts;
        msc->sc_free_interrupts = virtio_mmio_cmdline_free_interrupts;

        virtio_mmio_common_attach(msc);
        virtio_mmio_cmdline_rescan(self, "virtio", NULL);

        return 0;
}

static int
virtio_mmio_cmdline_detach(device_t self, int flags)
{
        struct virtio_mmio_cmdline_softc * const sc = device_private(self);
        struct virtio_mmio_softc * const msc = &sc->sc_msc;

        return virtio_mmio_common_detach(msc, flags);
}

static int
virtio_mmio_cmdline_rescan(device_t self, const char *ifattr, const int *locs)
{
        struct virtio_mmio_cmdline_softc *const sc = device_private(self);
        struct virtio_mmio_softc *const msc = &sc->sc_msc;
        struct virtio_softc *const vsc = &msc->sc_sc;
        struct virtio_attach_args va;

        if (vsc->sc_child)
                return 0;

        memset(&va, 0, sizeof(va));
        va.sc_childdevid = vsc->sc_childdevid;

        config_found(self, &va, NULL, CFARGS_NONE);

        if (virtio_attach_failed(vsc))
                return 0;

        return 0;
}


static int
virtio_mmio_cmdline_alloc_interrupts(struct virtio_mmio_softc *msc)
{
        struct virtio_mmio_cmdline_softc *const sc =
            (struct virtio_mmio_cmdline_softc *)msc;
        struct virtio_softc *const vsc = &msc->sc_sc;
        struct ioapic_softc *ioapic;
        struct pic *pic;
        int irq = sc->margs.irq;
        int pin = irq;
        bool mpsafe;

        ioapic = ioapic_find_bybase(irq);

        if (ioapic != NULL) {
                KASSERT(ioapic->sc_pic.pic_type == PIC_IOAPIC);
                pic = &ioapic->sc_pic;
                pin = irq - pic->pic_vecbase;
                irq = -1;
        } else
                pic = &i8259_pic;

        mpsafe = (0 != (vsc->sc_flags & VIRTIO_F_INTR_MPSAFE));

        msc->sc_ih = intr_establish_xname(irq, pic, pin, IST_LEVEL, vsc->sc_ipl,
            virtio_mmio_intr, msc, mpsafe, device_xname(vsc->sc_dev));
        if (msc->sc_ih == NULL) {
                aprint_error_dev(vsc->sc_dev,
                    "failed to establish interrupt\n");
                return -1;
        }
        aprint_normal_dev(vsc->sc_dev, "interrupting on %d\n", irq);

        return 0;
}

static void
virtio_mmio_cmdline_free_interrupts(struct virtio_mmio_softc *msc)
{
        if (msc->sc_ih != NULL) {
                intr_disestablish(msc->sc_ih);
                msc->sc_ih = NULL;
        }
}