mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2026-06-08 01:54:44 +00:00
initial import
internal commit: 0ab1ea615e5cfbb0687a9d593a86a7b774386076 Signed-off-by: Anthony Xu <anthony.xu@intel.com>
This commit is contained in:
2481
devicemodel/hw/pci/ahci.c
Normal file
2481
devicemodel/hw/pci/ahci.c
Normal file
File diff suppressed because it is too large
Load Diff
2156
devicemodel/hw/pci/core.c
Normal file
2156
devicemodel/hw/pci/core.c
Normal file
File diff suppressed because it is too large
Load Diff
68
devicemodel/hw/pci/hostbridge.c
Normal file
68
devicemodel/hw/pci/hostbridge.c
Normal file
@@ -0,0 +1,68 @@
|
||||
/*-
|
||||
* Copyright (c) 2011 NetApp, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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 NETAPP, INC ``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 NETAPP, INC 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.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#include <pthread.h>
|
||||
#include "pci_core.h"
|
||||
|
||||
static int
|
||||
pci_hostbridge_init(struct vmctx *ctx, struct pci_vdev *pi, char *opts)
|
||||
{
|
||||
/* config space */
|
||||
pci_set_cfgdata16(pi, PCIR_VENDOR, 0x1275); /* NetApp */
|
||||
pci_set_cfgdata16(pi, PCIR_DEVICE, 0x1275); /* NetApp */
|
||||
pci_set_cfgdata8(pi, PCIR_HDRTYPE, PCIM_HDRTYPE_NORMAL);
|
||||
pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_BRIDGE);
|
||||
pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_BRIDGE_HOST);
|
||||
|
||||
pci_emul_add_pciecap(pi, PCIEM_TYPE_ROOT_PORT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pci_amd_hostbridge_init(struct vmctx *ctx, struct pci_vdev *pi, char *opts)
|
||||
{
|
||||
(void) pci_hostbridge_init(ctx, pi, opts);
|
||||
pci_set_cfgdata16(pi, PCIR_VENDOR, 0x1022); /* AMD */
|
||||
pci_set_cfgdata16(pi, PCIR_DEVICE, 0x7432); /* made up */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct pci_vdev_ops pci_ops_amd_hostbridge = {
|
||||
.class_name = "amd_hostbridge",
|
||||
.vdev_init = pci_amd_hostbridge_init,
|
||||
};
|
||||
DEFINE_PCI_DEVTYPE(pci_ops_amd_hostbridge);
|
||||
|
||||
struct pci_vdev_ops pci_ops_hostbridge = {
|
||||
.class_name = "hostbridge",
|
||||
.vdev_init = pci_hostbridge_init,
|
||||
};
|
||||
DEFINE_PCI_DEVTYPE(pci_ops_hostbridge);
|
||||
344
devicemodel/hw/pci/irq.c
Normal file
344
devicemodel/hw/pci/irq.c
Normal file
@@ -0,0 +1,344 @@
|
||||
/*-
|
||||
* Copyright (c) 2014 Hudson River Trading LLC
|
||||
* Written by: John H. Baldwin <jhb@FreeBSD.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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/cdefs.h>
|
||||
#include <sys/param.h>
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "acpi.h"
|
||||
#include "vmm.h"
|
||||
#include "vmmapi.h"
|
||||
#include "inout.h"
|
||||
#include "pci_core.h"
|
||||
#include "irq.h"
|
||||
#include "lpc.h"
|
||||
|
||||
/*
|
||||
* Implement an 8 pin PCI interrupt router compatible with the router
|
||||
* present on Intel's ICH10 chip.
|
||||
*/
|
||||
|
||||
/* Fields in each PIRQ register. */
|
||||
#define PIRQ_DIS 0x80
|
||||
#define PIRQ_IRQ 0x0f
|
||||
|
||||
/* Only IRQs 3-7, 9-12, and 14-15 are permitted. */
|
||||
#define PERMITTED_IRQS 0xdef8
|
||||
#define IRQ_PERMITTED(irq) (((1U << (irq)) & PERMITTED_IRQS) != 0)
|
||||
|
||||
/* IRQ count to disable an IRQ. */
|
||||
#define IRQ_DISABLED 0xff
|
||||
|
||||
static struct pirq {
|
||||
uint8_t reg;
|
||||
int use_count;
|
||||
int active_count;
|
||||
pthread_mutex_t lock;
|
||||
} pirqs[8];
|
||||
|
||||
static u_char irq_counts[16];
|
||||
static int pirq_cold = 1;
|
||||
|
||||
/*
|
||||
* Returns true if this pin is enabled with a valid IRQ. Setting the
|
||||
* register to a reserved IRQ causes interrupts to not be asserted as
|
||||
* if the pin was disabled.
|
||||
*/
|
||||
static bool
|
||||
pirq_valid_irq(int reg)
|
||||
{
|
||||
if (reg & PIRQ_DIS)
|
||||
return false;
|
||||
return IRQ_PERMITTED(reg & PIRQ_IRQ);
|
||||
}
|
||||
|
||||
uint8_t
|
||||
pirq_read(int pin)
|
||||
{
|
||||
assert(pin > 0 && pin <= nitems(pirqs));
|
||||
return pirqs[pin - 1].reg;
|
||||
}
|
||||
|
||||
void
|
||||
pirq_write(struct vmctx *ctx, int pin, uint8_t val)
|
||||
{
|
||||
struct pirq *pirq;
|
||||
|
||||
assert(pin > 0 && pin <= nitems(pirqs));
|
||||
pirq = &pirqs[pin - 1];
|
||||
pthread_mutex_lock(&pirq->lock);
|
||||
if (pirq->reg != (val & (PIRQ_DIS | PIRQ_IRQ))) {
|
||||
if (pirq->active_count != 0 && pirq_valid_irq(pirq->reg))
|
||||
vm_isa_deassert_irq(ctx, pirq->reg & PIRQ_IRQ, -1);
|
||||
pirq->reg = val & (PIRQ_DIS | PIRQ_IRQ);
|
||||
if (pirq->active_count != 0 && pirq_valid_irq(pirq->reg))
|
||||
vm_isa_assert_irq(ctx, pirq->reg & PIRQ_IRQ, -1);
|
||||
}
|
||||
pthread_mutex_unlock(&pirq->lock);
|
||||
}
|
||||
|
||||
void
|
||||
pci_irq_reserve(int irq)
|
||||
{
|
||||
assert(irq >= 0 && irq < nitems(irq_counts));
|
||||
assert(pirq_cold);
|
||||
assert(irq_counts[irq] == 0 || irq_counts[irq] == IRQ_DISABLED);
|
||||
irq_counts[irq] = IRQ_DISABLED;
|
||||
}
|
||||
|
||||
void
|
||||
pci_irq_use(int irq)
|
||||
{
|
||||
assert(irq >= 0 && irq < nitems(irq_counts));
|
||||
assert(pirq_cold);
|
||||
assert(irq_counts[irq] != IRQ_DISABLED);
|
||||
irq_counts[irq]++;
|
||||
}
|
||||
|
||||
void
|
||||
pci_irq_init(struct vmctx *ctx)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nitems(pirqs); i++) {
|
||||
pirqs[i].reg = PIRQ_DIS;
|
||||
pirqs[i].use_count = 0;
|
||||
pirqs[i].active_count = 0;
|
||||
pthread_mutex_init(&pirqs[i].lock, NULL);
|
||||
}
|
||||
for (i = 0; i < nitems(irq_counts); i++) {
|
||||
if (IRQ_PERMITTED(i))
|
||||
irq_counts[i] = 0;
|
||||
else
|
||||
irq_counts[i] = IRQ_DISABLED;
|
||||
}
|
||||
}
|
||||
|
||||
void pci_irq_deinit(struct vmctx *ctx)
|
||||
{
|
||||
pirq_cold = 1;
|
||||
}
|
||||
|
||||
void
|
||||
pci_irq_assert(struct pci_vdev *dev)
|
||||
{
|
||||
struct pirq *pirq;
|
||||
|
||||
if (dev->lintr.pirq_pin > 0) {
|
||||
assert(dev->lintr.pirq_pin <= nitems(pirqs));
|
||||
pirq = &pirqs[dev->lintr.pirq_pin - 1];
|
||||
pthread_mutex_lock(&pirq->lock);
|
||||
pirq->active_count++;
|
||||
if (pirq->active_count == 1 && pirq_valid_irq(pirq->reg)) {
|
||||
vm_isa_assert_irq(dev->vmctx, pirq->reg & PIRQ_IRQ,
|
||||
dev->lintr.ioapic_irq);
|
||||
pthread_mutex_unlock(&pirq->lock);
|
||||
return;
|
||||
}
|
||||
pthread_mutex_unlock(&pirq->lock);
|
||||
}
|
||||
vm_ioapic_assert_irq(dev->vmctx, dev->lintr.ioapic_irq);
|
||||
}
|
||||
|
||||
void
|
||||
pci_irq_deassert(struct pci_vdev *dev)
|
||||
{
|
||||
struct pirq *pirq;
|
||||
|
||||
if (dev->lintr.pirq_pin > 0) {
|
||||
assert(dev->lintr.pirq_pin <= nitems(pirqs));
|
||||
pirq = &pirqs[dev->lintr.pirq_pin - 1];
|
||||
pthread_mutex_lock(&pirq->lock);
|
||||
pirq->active_count--;
|
||||
if (pirq->active_count == 0 && pirq_valid_irq(pirq->reg)) {
|
||||
vm_isa_deassert_irq(dev->vmctx, pirq->reg & PIRQ_IRQ,
|
||||
dev->lintr.ioapic_irq);
|
||||
pthread_mutex_unlock(&pirq->lock);
|
||||
return;
|
||||
}
|
||||
pthread_mutex_unlock(&pirq->lock);
|
||||
}
|
||||
vm_ioapic_deassert_irq(dev->vmctx, dev->lintr.ioapic_irq);
|
||||
}
|
||||
|
||||
int
|
||||
pirq_alloc_pin(struct pci_vdev *dev)
|
||||
{
|
||||
int best_count, best_irq, best_pin, irq, pin;
|
||||
|
||||
pirq_cold = 0;
|
||||
|
||||
/* Find the least-used PIRQ pin. */
|
||||
best_pin = 0;
|
||||
best_count = pirqs[0].use_count;
|
||||
for (pin = 1; pin < nitems(pirqs); pin++) {
|
||||
if (pirqs[pin].use_count < best_count) {
|
||||
best_pin = pin;
|
||||
best_count = pirqs[pin].use_count;
|
||||
}
|
||||
}
|
||||
pirqs[best_pin].use_count++;
|
||||
|
||||
/* Second, route this pin to an IRQ. */
|
||||
if (pirqs[best_pin].reg == PIRQ_DIS) {
|
||||
best_irq = -1;
|
||||
best_count = 0;
|
||||
for (irq = 0; irq < nitems(irq_counts); irq++) {
|
||||
if (irq_counts[irq] == IRQ_DISABLED)
|
||||
continue;
|
||||
if (best_irq == -1 || irq_counts[irq] < best_count) {
|
||||
best_irq = irq;
|
||||
best_count = irq_counts[irq];
|
||||
}
|
||||
}
|
||||
assert(best_irq >= 0);
|
||||
irq_counts[best_irq]++;
|
||||
pirqs[best_pin].reg = best_irq;
|
||||
}
|
||||
|
||||
return (best_pin + 1);
|
||||
}
|
||||
|
||||
int
|
||||
pirq_irq(int pin)
|
||||
{
|
||||
assert(pin > 0 && pin <= nitems(pirqs));
|
||||
return (pirqs[pin - 1].reg & PIRQ_IRQ);
|
||||
}
|
||||
|
||||
/* XXX: Generate $PIR table. */
|
||||
|
||||
static void
|
||||
pirq_dsdt(void)
|
||||
{
|
||||
char *irq_prs, *old;
|
||||
int irq, pin;
|
||||
|
||||
irq_prs = NULL;
|
||||
for (irq = 0; irq < nitems(irq_counts); irq++) {
|
||||
if (!IRQ_PERMITTED(irq))
|
||||
continue;
|
||||
if (irq_prs == NULL)
|
||||
asprintf(&irq_prs, "%d", irq);
|
||||
else {
|
||||
old = irq_prs;
|
||||
asprintf(&irq_prs, "%s,%d", old, irq);
|
||||
free(old);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A helper method to validate a link register's value. This
|
||||
* duplicates pirq_valid_irq().
|
||||
*/
|
||||
dsdt_line("");
|
||||
dsdt_line("Method (PIRV, 1, NotSerialized)");
|
||||
dsdt_line("{");
|
||||
dsdt_line(" If (And (Arg0, 0x%02X))", PIRQ_DIS);
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Return (0x00)");
|
||||
dsdt_line(" }");
|
||||
dsdt_line(" And (Arg0, 0x%02X, Local0)", PIRQ_IRQ);
|
||||
dsdt_line(" If (LLess (Local0, 0x03))");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Return (0x00)");
|
||||
dsdt_line(" }");
|
||||
dsdt_line(" If (LEqual (Local0, 0x08))");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Return (0x00)");
|
||||
dsdt_line(" }");
|
||||
dsdt_line(" If (LEqual (Local0, 0x0D))");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Return (0x00)");
|
||||
dsdt_line(" }");
|
||||
dsdt_line(" Return (0x01)");
|
||||
dsdt_line("}");
|
||||
|
||||
for (pin = 0; pin < nitems(pirqs); pin++) {
|
||||
dsdt_line("");
|
||||
dsdt_line("Device (LNK%c)", 'A' + pin);
|
||||
dsdt_line("{");
|
||||
dsdt_line(" Name (_HID, EisaId (\"PNP0C0F\"))");
|
||||
dsdt_line(" Name (_UID, 0x%02X)", pin + 1);
|
||||
dsdt_line(" Method (_STA, 0, NotSerialized)");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" If (PIRV (PIR%c))", 'A' + pin);
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Return (0x0B)");
|
||||
dsdt_line(" }");
|
||||
dsdt_line(" Else");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Return (0x09)");
|
||||
dsdt_line(" }");
|
||||
dsdt_line(" }");
|
||||
dsdt_line(" Name (_PRS, ResourceTemplate ()");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" IRQ (Level, ActiveLow, Shared, )");
|
||||
dsdt_line(" {%s}", irq_prs);
|
||||
dsdt_line(" })");
|
||||
dsdt_line(" Name (CB%02X, ResourceTemplate ()", pin + 1);
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" IRQ (Level, ActiveLow, Shared, )");
|
||||
dsdt_line(" {}");
|
||||
dsdt_line(" })");
|
||||
dsdt_line(" CreateWordField (CB%02X, 0x01, CIR%c)",
|
||||
pin + 1, 'A' + pin);
|
||||
dsdt_line(" Method (_CRS, 0, NotSerialized)");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" And (PIR%c, 0x%02X, Local0)", 'A' + pin,
|
||||
PIRQ_DIS | PIRQ_IRQ);
|
||||
dsdt_line(" If (PIRV (Local0))");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" ShiftLeft (0x01, Local0, CIR%c)", 'A' + pin);
|
||||
dsdt_line(" }");
|
||||
dsdt_line(" Else");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Store (0x00, CIR%c)", 'A' + pin);
|
||||
dsdt_line(" }");
|
||||
dsdt_line(" Return (CB%02X)", pin + 1);
|
||||
dsdt_line(" }");
|
||||
dsdt_line(" Method (_DIS, 0, NotSerialized)");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Store (0x80, PIR%c)", 'A' + pin);
|
||||
dsdt_line(" }");
|
||||
dsdt_line(" Method (_SRS, 1, NotSerialized)");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" CreateWordField (Arg0, 0x01, SIR%c)", 'A' + pin);
|
||||
dsdt_line(" FindSetRightBit (SIR%c, Local0)", 'A' + pin);
|
||||
dsdt_line(" Store (Decrement (Local0), PIR%c)", 'A' + pin);
|
||||
dsdt_line(" }");
|
||||
dsdt_line("}");
|
||||
}
|
||||
free(irq_prs);
|
||||
}
|
||||
LPC_DSDT(pirq_dsdt);
|
||||
455
devicemodel/hw/pci/lpc.c
Normal file
455
devicemodel/hw/pci/lpc.c
Normal file
@@ -0,0 +1,455 @@
|
||||
/*-
|
||||
* Copyright (c) 2013 Neel Natu <neel@freebsd.org>
|
||||
* Copyright (c) 2013 Tycho Nightingale <tycho.nightingale@pluribusnetworks.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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 NETAPP, INC ``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 NETAPP, INC 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.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "vmm.h"
|
||||
#include "vmmapi.h"
|
||||
#include "acpi.h"
|
||||
#include "inout.h"
|
||||
#include "pci_core.h"
|
||||
#include "irq.h"
|
||||
#include "lpc.h"
|
||||
#include "uart_core.h"
|
||||
|
||||
#define IO_ICU1 0x20
|
||||
#define IO_ICU2 0xA0
|
||||
|
||||
SET_DECLARE(lpc_dsdt_set, struct lpc_dsdt);
|
||||
SET_DECLARE(lpc_sysres_set, struct lpc_sysres);
|
||||
|
||||
#define ELCR_PORT 0x4d0
|
||||
SYSRES_IO(ELCR_PORT, 2);
|
||||
|
||||
#define IO_TIMER1_PORT 0x40
|
||||
|
||||
#define NMISC_PORT 0x61
|
||||
SYSRES_IO(NMISC_PORT, 1);
|
||||
|
||||
static struct pci_vdev *lpc_bridge;
|
||||
|
||||
#define LPC_UART_NUM 2
|
||||
static struct lpc_uart_vdev {
|
||||
struct uart_vdev *uart;
|
||||
const char *opts;
|
||||
int iobase;
|
||||
int irq;
|
||||
int enabled;
|
||||
} lpc_uart_vdev[LPC_UART_NUM];
|
||||
|
||||
static const char *lpc_uart_names[LPC_UART_NUM] = { "COM1", "COM2" };
|
||||
|
||||
/*
|
||||
* LPC device configuration is in the following form:
|
||||
* <lpc_device_name>[,<options>]
|
||||
* For e.g. "com1,stdio"
|
||||
*/
|
||||
int
|
||||
lpc_device_parse(const char *opts)
|
||||
{
|
||||
int unit, error;
|
||||
char *str, *cpy, *lpcdev;
|
||||
|
||||
error = -1;
|
||||
str = cpy = strdup(opts);
|
||||
lpcdev = strsep(&str, ",");
|
||||
if (lpcdev != NULL) {
|
||||
for (unit = 0; unit < LPC_UART_NUM; unit++) {
|
||||
if (strcasecmp(lpcdev, lpc_uart_names[unit]) == 0) {
|
||||
lpc_uart_vdev[unit].opts = str;
|
||||
error = 0;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (error)
|
||||
free(cpy);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void
|
||||
lpc_uart_intr_assert(void *arg)
|
||||
{
|
||||
struct lpc_uart_vdev *lpc_uart = arg;
|
||||
|
||||
assert(lpc_uart->irq >= 0);
|
||||
|
||||
if (lpc_bridge)
|
||||
vm_isa_pulse_irq(lpc_bridge->vmctx,
|
||||
lpc_uart->irq,
|
||||
lpc_uart->irq);
|
||||
}
|
||||
|
||||
static void
|
||||
lpc_uart_intr_deassert(void *arg)
|
||||
{
|
||||
/*
|
||||
* The COM devices on the LPC bus generate edge triggered interrupts,
|
||||
* so nothing more to do here.
|
||||
*/
|
||||
}
|
||||
|
||||
static int
|
||||
lpc_uart_io_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
|
||||
uint32_t *eax, void *arg)
|
||||
{
|
||||
int offset;
|
||||
struct lpc_uart_vdev *lpc_uart = arg;
|
||||
|
||||
offset = port - lpc_uart->iobase;
|
||||
|
||||
switch (bytes) {
|
||||
case 1:
|
||||
if (in)
|
||||
*eax = uart_read(lpc_uart->uart, offset);
|
||||
else
|
||||
uart_write(lpc_uart->uart, offset, *eax);
|
||||
break;
|
||||
case 2:
|
||||
if (in) {
|
||||
*eax = uart_read(lpc_uart->uart, offset);
|
||||
*eax |= uart_read(lpc_uart->uart, offset + 1) << 8;
|
||||
} else {
|
||||
uart_write(lpc_uart->uart, offset, *eax);
|
||||
uart_write(lpc_uart->uart, offset + 1, *eax >> 8);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
lpc_init(struct vmctx *ctx)
|
||||
{
|
||||
struct lpc_uart_vdev *lpc_uart;
|
||||
struct inout_port iop;
|
||||
const char *name;
|
||||
int unit, error;
|
||||
|
||||
/* COM1 and COM2 */
|
||||
for (unit = 0; unit < LPC_UART_NUM; unit++) {
|
||||
lpc_uart = &lpc_uart_vdev[unit];
|
||||
name = lpc_uart_names[unit];
|
||||
|
||||
if (uart_legacy_alloc(unit,
|
||||
&lpc_uart->iobase,
|
||||
&lpc_uart->irq) != 0) {
|
||||
fprintf(stderr, "Unable to allocate resources for "
|
||||
"LPC device %s\n", name);
|
||||
return -1;
|
||||
}
|
||||
pci_irq_reserve(lpc_uart->irq);
|
||||
|
||||
lpc_uart->uart = uart_init(lpc_uart_intr_assert,
|
||||
lpc_uart_intr_deassert, lpc_uart);
|
||||
|
||||
if (uart_set_backend(lpc_uart->uart, lpc_uart->opts) != 0) {
|
||||
fprintf(stderr, "Unable to initialize backend '%s' "
|
||||
"for LPC device %s\n", lpc_uart->opts, name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bzero(&iop, sizeof(struct inout_port));
|
||||
iop.name = name;
|
||||
iop.port = lpc_uart->iobase;
|
||||
iop.size = UART_IO_BAR_SIZE;
|
||||
iop.flags = IOPORT_F_INOUT;
|
||||
iop.handler = lpc_uart_io_handler;
|
||||
iop.arg = lpc_uart;
|
||||
|
||||
error = register_inout(&iop);
|
||||
assert(error == 0);
|
||||
lpc_uart->enabled = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
lpc_deinit(struct vmctx *ctx)
|
||||
{
|
||||
struct lpc_uart_vdev *lpc_uart;
|
||||
int unit;
|
||||
|
||||
/* COM1 and COM2 */
|
||||
for (unit = 0; unit < LPC_UART_NUM; unit++) {
|
||||
lpc_uart = &lpc_uart_vdev[unit];
|
||||
|
||||
uart_legacy_dealloc(unit);
|
||||
uart_deinit(lpc_uart->uart);
|
||||
lpc_uart->uart = NULL;
|
||||
lpc_uart->enabled = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
pci_lpc_write_dsdt(struct pci_vdev *dev)
|
||||
{
|
||||
struct lpc_dsdt **ldpp, *ldp;
|
||||
|
||||
dsdt_line("");
|
||||
dsdt_line("Device (ISA)");
|
||||
dsdt_line("{");
|
||||
dsdt_line(" Name (_ADR, 0x%04X%04X)", dev->slot, dev->func);
|
||||
dsdt_line(" OperationRegion (LPCR, PCI_Config, 0x00, 0x100)");
|
||||
dsdt_line(" Field (LPCR, AnyAcc, NoLock, Preserve)");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Offset (0x60),");
|
||||
dsdt_line(" PIRA, 8,");
|
||||
dsdt_line(" PIRB, 8,");
|
||||
dsdt_line(" PIRC, 8,");
|
||||
dsdt_line(" PIRD, 8,");
|
||||
dsdt_line(" Offset (0x68),");
|
||||
dsdt_line(" PIRE, 8,");
|
||||
dsdt_line(" PIRF, 8,");
|
||||
dsdt_line(" PIRG, 8,");
|
||||
dsdt_line(" PIRH, 8");
|
||||
dsdt_line(" }");
|
||||
dsdt_line("");
|
||||
|
||||
dsdt_indent(1);
|
||||
SET_FOREACH(ldpp, lpc_dsdt_set) {
|
||||
ldp = *ldpp;
|
||||
ldp->handler();
|
||||
}
|
||||
|
||||
dsdt_line("");
|
||||
dsdt_line("Device (PIC)");
|
||||
dsdt_line("{");
|
||||
dsdt_line(" Name (_HID, EisaId (\"PNP0000\"))");
|
||||
dsdt_line(" Name (_CRS, ResourceTemplate ()");
|
||||
dsdt_line(" {");
|
||||
dsdt_indent(2);
|
||||
dsdt_fixed_ioport(IO_ICU1, 2);
|
||||
dsdt_fixed_ioport(IO_ICU2, 2);
|
||||
dsdt_fixed_irq(2);
|
||||
dsdt_unindent(2);
|
||||
dsdt_line(" })");
|
||||
dsdt_line("}");
|
||||
|
||||
dsdt_line("");
|
||||
dsdt_line("Device (TIMR)");
|
||||
dsdt_line("{");
|
||||
dsdt_line(" Name (_HID, EisaId (\"PNP0100\"))");
|
||||
dsdt_line(" Name (_CRS, ResourceTemplate ()");
|
||||
dsdt_line(" {");
|
||||
dsdt_indent(2);
|
||||
dsdt_fixed_ioport(IO_TIMER1_PORT, 4);
|
||||
dsdt_fixed_irq(0);
|
||||
dsdt_unindent(2);
|
||||
dsdt_line(" })");
|
||||
dsdt_line("}");
|
||||
dsdt_unindent(1);
|
||||
|
||||
dsdt_line("}");
|
||||
}
|
||||
|
||||
static void
|
||||
pci_lpc_sysres_dsdt(void)
|
||||
{
|
||||
struct lpc_sysres **lspp, *lsp;
|
||||
|
||||
dsdt_line("");
|
||||
dsdt_line("Device (SIO)");
|
||||
dsdt_line("{");
|
||||
dsdt_line(" Name (_HID, EisaId (\"PNP0C02\"))");
|
||||
dsdt_line(" Name (_CRS, ResourceTemplate ()");
|
||||
dsdt_line(" {");
|
||||
|
||||
dsdt_indent(2);
|
||||
SET_FOREACH(lspp, lpc_sysres_set) {
|
||||
lsp = *lspp;
|
||||
switch (lsp->type) {
|
||||
case LPC_SYSRES_IO:
|
||||
dsdt_fixed_ioport(lsp->base, lsp->length);
|
||||
break;
|
||||
case LPC_SYSRES_MEM:
|
||||
dsdt_fixed_mem32(lsp->base, lsp->length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
dsdt_unindent(2);
|
||||
|
||||
dsdt_line(" })");
|
||||
dsdt_line("}");
|
||||
}
|
||||
LPC_DSDT(pci_lpc_sysres_dsdt);
|
||||
|
||||
static void
|
||||
pci_lpc_uart_dsdt(void)
|
||||
{
|
||||
struct lpc_uart_vdev *lpc_uart;
|
||||
int unit;
|
||||
|
||||
for (unit = 0; unit < LPC_UART_NUM; unit++) {
|
||||
lpc_uart = &lpc_uart_vdev[unit];
|
||||
if (!lpc_uart->enabled)
|
||||
continue;
|
||||
dsdt_line("");
|
||||
dsdt_line("Device (%s)", lpc_uart_names[unit]);
|
||||
dsdt_line("{");
|
||||
dsdt_line(" Name (_HID, EisaId (\"PNP0501\"))");
|
||||
dsdt_line(" Name (_UID, %d)", unit + 1);
|
||||
dsdt_line(" Name (_CRS, ResourceTemplate ()");
|
||||
dsdt_line(" {");
|
||||
dsdt_indent(2);
|
||||
dsdt_fixed_ioport(lpc_uart->iobase, UART_IO_BAR_SIZE);
|
||||
dsdt_fixed_irq(lpc_uart->irq);
|
||||
dsdt_unindent(2);
|
||||
dsdt_line(" })");
|
||||
dsdt_line("}");
|
||||
}
|
||||
}
|
||||
LPC_DSDT(pci_lpc_uart_dsdt);
|
||||
|
||||
static int
|
||||
pci_lpc_cfgwrite(struct vmctx *ctx, int vcpu, struct pci_vdev *pi,
|
||||
int coff, int bytes, uint32_t val)
|
||||
{
|
||||
int pirq_pin;
|
||||
|
||||
if (bytes == 1) {
|
||||
pirq_pin = 0;
|
||||
if (coff >= 0x60 && coff <= 0x63)
|
||||
pirq_pin = coff - 0x60 + 1;
|
||||
if (coff >= 0x68 && coff <= 0x6b)
|
||||
pirq_pin = coff - 0x68 + 5;
|
||||
if (pirq_pin != 0) {
|
||||
pirq_write(ctx, pirq_pin, val);
|
||||
pci_set_cfgdata8(pi, coff, pirq_read(pirq_pin));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
pci_lpc_write(struct vmctx *ctx, int vcpu, struct pci_vdev *pi,
|
||||
int baridx, uint64_t offset, int size, uint64_t value)
|
||||
{
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
pci_lpc_read(struct vmctx *ctx, int vcpu, struct pci_vdev *pi,
|
||||
int baridx, uint64_t offset, int size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define LPC_DEV 0x7000
|
||||
#define LPC_VENDOR 0x8086
|
||||
|
||||
static int
|
||||
pci_lpc_init(struct vmctx *ctx, struct pci_vdev *pi, char *opts)
|
||||
{
|
||||
/*
|
||||
* Do not allow more than one LPC bridge to be configured.
|
||||
*/
|
||||
if (lpc_bridge != NULL) {
|
||||
fprintf(stderr, "Only one LPC bridge is allowed.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enforce that the LPC can only be configured on bus 0. This
|
||||
* simplifies the ACPI DSDT because it can provide a decode for
|
||||
* all legacy i/o ports behind bus 0.
|
||||
*/
|
||||
if (pi->bus != 0) {
|
||||
fprintf(stderr, "LPC bridge can be present only on bus 0.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (lpc_init(ctx) != 0)
|
||||
return -1;
|
||||
|
||||
/* initialize config space */
|
||||
pci_set_cfgdata16(pi, PCIR_DEVICE, LPC_DEV);
|
||||
pci_set_cfgdata16(pi, PCIR_VENDOR, LPC_VENDOR);
|
||||
pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_BRIDGE);
|
||||
pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_BRIDGE_ISA);
|
||||
|
||||
lpc_bridge = pi;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
pci_lpc_deinit(struct vmctx *ctx, struct pci_vdev *pi, char *opts)
|
||||
{
|
||||
lpc_bridge = NULL;
|
||||
lpc_deinit(ctx);
|
||||
}
|
||||
|
||||
char *
|
||||
lpc_pirq_name(int pin)
|
||||
{
|
||||
char *name;
|
||||
|
||||
if (lpc_bridge == NULL)
|
||||
return NULL;
|
||||
asprintf(&name, "\\_SB.PCI0.ISA.LNK%c,", 'A' + pin - 1);
|
||||
return name;
|
||||
}
|
||||
|
||||
void
|
||||
lpc_pirq_routed(void)
|
||||
{
|
||||
int pin;
|
||||
|
||||
if (lpc_bridge == NULL)
|
||||
return;
|
||||
|
||||
for (pin = 0; pin < 4; pin++)
|
||||
pci_set_cfgdata8(lpc_bridge, 0x60 + pin, pirq_read(pin + 1));
|
||||
for (pin = 0; pin < 4; pin++)
|
||||
pci_set_cfgdata8(lpc_bridge, 0x68 + pin, pirq_read(pin + 5));
|
||||
}
|
||||
|
||||
struct pci_vdev_ops pci_ops_lpc = {
|
||||
.class_name = "lpc",
|
||||
.vdev_init = pci_lpc_init,
|
||||
.vdev_deinit = pci_lpc_deinit,
|
||||
.vdev_write_dsdt = pci_lpc_write_dsdt,
|
||||
.vdev_cfgwrite = pci_lpc_cfgwrite,
|
||||
.vdev_barwrite = pci_lpc_write,
|
||||
.vdev_barread = pci_lpc_read
|
||||
};
|
||||
DEFINE_PCI_DEVTYPE(pci_ops_lpc);
|
||||
1617
devicemodel/hw/pci/passthrough.c
Normal file
1617
devicemodel/hw/pci/passthrough.c
Normal file
File diff suppressed because it is too large
Load Diff
115
devicemodel/hw/pci/uart.c
Normal file
115
devicemodel/hw/pci/uart.c
Normal file
@@ -0,0 +1,115 @@
|
||||
/*-
|
||||
* Copyright (c) 2012 NetApp, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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 NETAPP, INC ``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 NETAPP, INC 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.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "dm.h"
|
||||
#include "pci_core.h"
|
||||
#include "uart_core.h"
|
||||
|
||||
/*
|
||||
* Pick a PCI vid/did of a chip with a single uart at
|
||||
* BAR0, that most versions of FreeBSD can understand:
|
||||
* Siig CyberSerial 1-port.
|
||||
*/
|
||||
#define COM_VENDOR 0x131f
|
||||
#define COM_DEV 0x2000
|
||||
|
||||
static void
|
||||
pci_uart_intr_assert(void *arg)
|
||||
{
|
||||
struct pci_vdev *dev = arg;
|
||||
|
||||
pci_lintr_assert(dev);
|
||||
}
|
||||
|
||||
static void
|
||||
pci_uart_intr_deassert(void *arg)
|
||||
{
|
||||
struct pci_vdev *dev = arg;
|
||||
|
||||
pci_lintr_deassert(dev);
|
||||
}
|
||||
|
||||
static void
|
||||
pci_uart_write(struct vmctx *ctx, int vcpu, struct pci_vdev *dev,
|
||||
int baridx, uint64_t offset, int size, uint64_t value)
|
||||
{
|
||||
assert(baridx == 0);
|
||||
assert(size == 1);
|
||||
|
||||
uart_write(dev->arg, offset, value);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
pci_uart_read(struct vmctx *ctx, int vcpu, struct pci_vdev *dev,
|
||||
int baridx, uint64_t offset, int size)
|
||||
{
|
||||
uint8_t val;
|
||||
|
||||
assert(baridx == 0);
|
||||
assert(size == 1);
|
||||
|
||||
val = uart_read(dev->arg, offset);
|
||||
return val;
|
||||
}
|
||||
|
||||
static int
|
||||
pci_uart_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
||||
{
|
||||
struct uart_vdev *uart;
|
||||
|
||||
pci_emul_alloc_bar(dev, 0, PCIBAR_IO, UART_IO_BAR_SIZE);
|
||||
pci_lintr_request(dev);
|
||||
|
||||
/* initialize config space */
|
||||
pci_set_cfgdata16(dev, PCIR_DEVICE, COM_DEV);
|
||||
pci_set_cfgdata16(dev, PCIR_VENDOR, COM_VENDOR);
|
||||
pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_SIMPLECOMM);
|
||||
|
||||
uart = uart_init(pci_uart_intr_assert, pci_uart_intr_deassert, dev);
|
||||
dev->arg = uart;
|
||||
|
||||
if (uart_set_backend(uart, opts) != 0) {
|
||||
fprintf(stderr, "Unable to initialize backend '%s' for "
|
||||
"pci uart at %d:%d\n", opts, dev->slot, dev->func);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct pci_vdev_ops pci_ops_com = {
|
||||
.class_name = "uart",
|
||||
.vdev_init = pci_uart_init,
|
||||
.vdev_barwrite = pci_uart_write,
|
||||
.vdev_barread = pci_uart_read
|
||||
};
|
||||
DEFINE_PCI_DEVTYPE(pci_ops_com);
|
||||
790
devicemodel/hw/pci/virtio/virtio.c
Normal file
790
devicemodel/hw/pci/virtio/virtio.c
Normal file
@@ -0,0 +1,790 @@
|
||||
/*-
|
||||
* Copyright (c) 2013 Chris Torek <torek @ torek net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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/cdefs.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/uio.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "dm.h"
|
||||
#include "pci_core.h"
|
||||
#include "virtio.h"
|
||||
|
||||
/*
|
||||
* Functions for dealing with generalized "virtual devices" as
|
||||
* defined by <https://www.google.com/#output=search&q=virtio+spec>
|
||||
*/
|
||||
|
||||
/*
|
||||
* In case we decide to relax the "virtio struct comes at the
|
||||
* front of virtio-based device struct" constraint, let's use
|
||||
* this to convert.
|
||||
*/
|
||||
#define DEV_STRUCT(vs) ((void *)(vs))
|
||||
|
||||
/*
|
||||
* Link a virtio_base to its constants, the virtio device, and
|
||||
* the PCI emulation.
|
||||
*/
|
||||
void
|
||||
virtio_linkup(struct virtio_base *base, struct virtio_ops *vops,
|
||||
void *pci_virtio_dev, struct pci_vdev *dev,
|
||||
struct virtio_vq_info *queues)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* base and pci_virtio_dev addresses must match */
|
||||
assert((void *)base == pci_virtio_dev);
|
||||
base->vops = vops;
|
||||
base->dev = dev;
|
||||
dev->arg = base;
|
||||
|
||||
base->queues = queues;
|
||||
for (i = 0; i < vops->nvq; i++) {
|
||||
queues[i].base = base;
|
||||
queues[i].num = i;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset device (device-wide). This erases all queues, i.e.,
|
||||
* all the queues become invalid (though we don't wipe out the
|
||||
* internal pointers, we just clear the VQ_ALLOC flag).
|
||||
*
|
||||
* It resets negotiated features to "none".
|
||||
*
|
||||
* If MSI-X is enabled, this also resets all the vectors to NO_VECTOR.
|
||||
*/
|
||||
void
|
||||
virtio_reset_dev(struct virtio_base *base)
|
||||
{
|
||||
struct virtio_vq_info *vq;
|
||||
int i, nvq;
|
||||
|
||||
/* if (base->mtx) */
|
||||
/* assert(pthread_mutex_isowned_np(base->mtx)); */
|
||||
|
||||
nvq = base->vops->nvq;
|
||||
for (vq = base->queues, i = 0; i < nvq; vq++, i++) {
|
||||
vq->flags = 0;
|
||||
vq->last_avail = 0;
|
||||
vq->save_used = 0;
|
||||
vq->pfn = 0;
|
||||
vq->msix_idx = VIRTIO_MSI_NO_VECTOR;
|
||||
}
|
||||
base->negotiated_caps = 0;
|
||||
base->curq = 0;
|
||||
/* base->status = 0; -- redundant */
|
||||
if (base->isr)
|
||||
pci_lintr_deassert(base->dev);
|
||||
base->isr = 0;
|
||||
base->msix_cfg_idx = VIRTIO_MSI_NO_VECTOR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set I/O BAR (usually 0) to map PCI config registers.
|
||||
*/
|
||||
void
|
||||
virtio_set_io_bar(struct virtio_base *base, int barnum)
|
||||
{
|
||||
size_t size;
|
||||
|
||||
/*
|
||||
* ??? should we use CFG0 if MSI-X is disabled?
|
||||
* Existing code did not...
|
||||
*/
|
||||
size = VIRTIO_CR_CFG1 + base->vops->cfgsize;
|
||||
pci_emul_alloc_bar(base->dev, barnum, PCIBAR_IO, size);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize MSI-X vector capabilities if we're to use MSI-X,
|
||||
* or MSI capabilities if not.
|
||||
*
|
||||
* We assume we want one MSI-X vector per queue, here, plus one
|
||||
* for the config vec.
|
||||
*/
|
||||
int
|
||||
virtio_intr_init(struct virtio_base *base, int barnum, int use_msix)
|
||||
{
|
||||
int nvec;
|
||||
|
||||
if (use_msix) {
|
||||
base->flags |= VIRTIO_USE_MSIX;
|
||||
VIRTIO_BASE_LOCK(base);
|
||||
virtio_reset_dev(base); /* set all vectors to NO_VECTOR */
|
||||
VIRTIO_BASE_UNLOCK(base);
|
||||
nvec = base->vops->nvq + 1;
|
||||
if (pci_emul_add_msixcap(base->dev, nvec, barnum))
|
||||
return -1;
|
||||
} else
|
||||
base->flags &= ~VIRTIO_USE_MSIX;
|
||||
|
||||
/* Only 1 MSI vector for acrn-dm */
|
||||
pci_emul_add_msicap(base->dev, 1);
|
||||
|
||||
/* Legacy interrupts are mandatory for virtio devices */
|
||||
pci_lintr_request(base->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize MSI-X vector capabilities if we're to use MSI-X,
|
||||
* or MSI capabilities if not.
|
||||
*
|
||||
* Wrapper function for virtio_intr_init() since by default we
|
||||
* will use bar 1 for MSI-X.
|
||||
*/
|
||||
int
|
||||
virtio_interrupt_init(struct virtio_base *base, int use_msix)
|
||||
{
|
||||
return virtio_intr_init(base, 1, use_msix);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the currently-selected virtio queue (base->curq).
|
||||
* The guest just gave us a page frame number, from which we can
|
||||
* calculate the addresses of the queue.
|
||||
*/
|
||||
void
|
||||
virtio_vq_init(struct virtio_base *base, uint32_t pfn)
|
||||
{
|
||||
struct virtio_vq_info *vq;
|
||||
uint64_t phys;
|
||||
size_t size;
|
||||
char *vb;
|
||||
|
||||
vq = &base->queues[base->curq];
|
||||
vq->pfn = pfn;
|
||||
phys = (uint64_t)pfn << VRING_PAGE_BITS;
|
||||
size = vring_size(vq->qsize);
|
||||
vb = paddr_guest2host(base->dev->vmctx, phys, size);
|
||||
|
||||
/* First page(s) are descriptors... */
|
||||
vq->desc = (struct virtio_desc *)vb;
|
||||
vb += vq->qsize * sizeof(struct virtio_desc);
|
||||
|
||||
/* ... immediately followed by "avail" ring (entirely uint16_t's) */
|
||||
vq->avail = (struct vring_avail *)vb;
|
||||
vb += (2 + vq->qsize + 1) * sizeof(uint16_t);
|
||||
|
||||
/* Then it's rounded up to the next page... */
|
||||
vb = (char *)roundup2((uintptr_t)vb, VRING_ALIGN);
|
||||
|
||||
/* ... and the last page(s) are the used ring. */
|
||||
vq->used = (struct vring_used *)vb;
|
||||
|
||||
/* Mark queue as allocated, and start at 0 when we use it. */
|
||||
vq->flags = VQ_ALLOC;
|
||||
vq->last_avail = 0;
|
||||
vq->save_used = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper inline for vq_getchain(): record the i'th "real"
|
||||
* descriptor.
|
||||
*/
|
||||
static inline void
|
||||
_vq_record(int i, volatile struct virtio_desc *vd, struct vmctx *ctx,
|
||||
struct iovec *iov, int n_iov, uint16_t *flags) {
|
||||
|
||||
if (i >= n_iov)
|
||||
return;
|
||||
iov[i].iov_base = paddr_guest2host(ctx, vd->addr, vd->len);
|
||||
iov[i].iov_len = vd->len;
|
||||
if (flags != NULL)
|
||||
flags[i] = vd->flags;
|
||||
}
|
||||
#define VQ_MAX_DESCRIPTORS 512 /* see below */
|
||||
|
||||
/*
|
||||
* Examine the chain of descriptors starting at the "next one" to
|
||||
* make sure that they describe a sensible request. If so, return
|
||||
* the number of "real" descriptors that would be needed/used in
|
||||
* acting on this request. This may be smaller than the number of
|
||||
* available descriptors, e.g., if there are two available but
|
||||
* they are two separate requests, this just returns 1. Or, it
|
||||
* may be larger: if there are indirect descriptors involved,
|
||||
* there may only be one descriptor available but it may be an
|
||||
* indirect pointing to eight more. We return 8 in this case,
|
||||
* i.e., we do not count the indirect descriptors, only the "real"
|
||||
* ones.
|
||||
*
|
||||
* Basically, this vets the flags and vd_next field of each
|
||||
* descriptor and tells you how many are involved. Since some may
|
||||
* be indirect, this also needs the vmctx (in the pci_vdev
|
||||
* at base->dev) so that it can find indirect descriptors.
|
||||
*
|
||||
* As we process each descriptor, we copy and adjust it (guest to
|
||||
* host address wise, also using the vmtctx) into the given iov[]
|
||||
* array (of the given size). If the array overflows, we stop
|
||||
* placing values into the array but keep processing descriptors,
|
||||
* up to VQ_MAX_DESCRIPTORS, before giving up and returning -1.
|
||||
* So you, the caller, must not assume that iov[] is as big as the
|
||||
* return value (you can process the same thing twice to allocate
|
||||
* a larger iov array if needed, or supply a zero length to find
|
||||
* out how much space is needed).
|
||||
*
|
||||
* If you want to verify the WRITE flag on each descriptor, pass a
|
||||
* non-NULL "flags" pointer to an array of "uint16_t" of the same size
|
||||
* as n_iov and we'll copy each flags field after unwinding any
|
||||
* indirects.
|
||||
*
|
||||
* If some descriptor(s) are invalid, this prints a diagnostic message
|
||||
* and returns -1. If no descriptors are ready now it simply returns 0.
|
||||
*
|
||||
* You are assumed to have done a vq_ring_ready() if needed (note
|
||||
* that vq_has_descs() does one).
|
||||
*/
|
||||
int
|
||||
vq_getchain(struct virtio_vq_info *vq, uint16_t *pidx,
|
||||
struct iovec *iov, int n_iov, uint16_t *flags)
|
||||
{
|
||||
int i;
|
||||
u_int ndesc, n_indir;
|
||||
u_int idx, next;
|
||||
|
||||
volatile struct virtio_desc *vdir, *vindir, *vp;
|
||||
struct vmctx *ctx;
|
||||
struct virtio_base *base;
|
||||
const char *name;
|
||||
|
||||
base = vq->base;
|
||||
name = base->vops->name;
|
||||
|
||||
/*
|
||||
* Note: it's the responsibility of the guest not to
|
||||
* update vq->avail->idx until all of the descriptors
|
||||
* the guest has written are valid (including all their
|
||||
* next fields and vd_flags).
|
||||
*
|
||||
* Compute (last_avail - idx) in integers mod 2**16. This is
|
||||
* the number of descriptors the device has made available
|
||||
* since the last time we updated vq->last_avail.
|
||||
*
|
||||
* We just need to do the subtraction as an unsigned int,
|
||||
* then trim off excess bits.
|
||||
*/
|
||||
idx = vq->last_avail;
|
||||
ndesc = (uint16_t)((u_int)vq->avail->idx - idx);
|
||||
if (ndesc == 0)
|
||||
return 0;
|
||||
if (ndesc > vq->qsize) {
|
||||
/* XXX need better way to diagnose issues */
|
||||
fprintf(stderr,
|
||||
"%s: ndesc (%u) out of range, driver confused?\r\n",
|
||||
name, (u_int)ndesc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now count/parse "involved" descriptors starting from
|
||||
* the head of the chain.
|
||||
*
|
||||
* To prevent loops, we could be more complicated and
|
||||
* check whether we're re-visiting a previously visited
|
||||
* index, but we just abort if the count gets excessive.
|
||||
*/
|
||||
ctx = base->dev->vmctx;
|
||||
*pidx = next = vq->avail->ring[idx & (vq->qsize - 1)];
|
||||
vq->last_avail++;
|
||||
for (i = 0; i < VQ_MAX_DESCRIPTORS; next = vdir->next) {
|
||||
if (next >= vq->qsize) {
|
||||
fprintf(stderr,
|
||||
"%s: descriptor index %u out of range, "
|
||||
"driver confused?\r\n",
|
||||
name, next);
|
||||
return -1;
|
||||
}
|
||||
vdir = &vq->desc[next];
|
||||
if ((vdir->flags & VRING_DESC_F_INDIRECT) == 0) {
|
||||
_vq_record(i, vdir, ctx, iov, n_iov, flags);
|
||||
i++;
|
||||
} else if ((base->vops->hv_caps &
|
||||
VIRTIO_RING_F_INDIRECT_DESC) == 0) {
|
||||
fprintf(stderr,
|
||||
"%s: descriptor has forbidden INDIRECT flag, "
|
||||
"driver confused?\r\n",
|
||||
name);
|
||||
return -1;
|
||||
} else {
|
||||
n_indir = vdir->len / 16;
|
||||
if ((vdir->len & 0xf) || n_indir == 0) {
|
||||
fprintf(stderr,
|
||||
"%s: invalid indir len 0x%x, "
|
||||
"driver confused?\r\n",
|
||||
name, (u_int)vdir->len);
|
||||
return -1;
|
||||
}
|
||||
vindir = paddr_guest2host(ctx,
|
||||
vdir->addr, vdir->len);
|
||||
/*
|
||||
* Indirects start at the 0th, then follow
|
||||
* their own embedded "next"s until those run
|
||||
* out. Each one's indirect flag must be off
|
||||
* (we don't really have to check, could just
|
||||
* ignore errors...).
|
||||
*/
|
||||
next = 0;
|
||||
for (;;) {
|
||||
vp = &vindir[next];
|
||||
if (vp->flags & VRING_DESC_F_INDIRECT) {
|
||||
fprintf(stderr,
|
||||
"%s: indirect desc has INDIR flag,"
|
||||
" driver confused?\r\n",
|
||||
name);
|
||||
return -1;
|
||||
}
|
||||
_vq_record(i, vp, ctx, iov, n_iov, flags);
|
||||
if (++i > VQ_MAX_DESCRIPTORS)
|
||||
goto loopy;
|
||||
if ((vp->flags & VRING_DESC_F_NEXT) == 0)
|
||||
break;
|
||||
next = vp->next;
|
||||
if (next >= n_indir) {
|
||||
fprintf(stderr,
|
||||
"%s: invalid next %u > %u, "
|
||||
"driver confused?\r\n",
|
||||
name, (u_int)next, n_indir);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((vdir->flags & VRING_DESC_F_NEXT) == 0)
|
||||
return i;
|
||||
}
|
||||
loopy:
|
||||
fprintf(stderr,
|
||||
"%s: descriptor loop? count > %d - driver confused?\r\n",
|
||||
name, i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the currently-first request chain back to the available queue.
|
||||
*
|
||||
* (This chain is the one you handled when you called vq_getchain()
|
||||
* and used its positive return value.)
|
||||
*/
|
||||
void
|
||||
vq_retchain(struct virtio_vq_info *vq)
|
||||
{
|
||||
vq->last_avail--;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return specified request chain to the guest, setting its I/O length
|
||||
* to the provided value.
|
||||
*
|
||||
* (This chain is the one you handled when you called vq_getchain()
|
||||
* and used its positive return value.)
|
||||
*/
|
||||
void
|
||||
vq_relchain(struct virtio_vq_info *vq, uint16_t idx, uint32_t iolen)
|
||||
{
|
||||
uint16_t uidx, mask;
|
||||
volatile struct vring_used *vuh;
|
||||
volatile struct virtio_used *vue;
|
||||
|
||||
/*
|
||||
* Notes:
|
||||
* - mask is N-1 where N is a power of 2 so computes x % N
|
||||
* - vuh points to the "used" data shared with guest
|
||||
* - vue points to the "used" ring entry we want to update
|
||||
* - head is the same value we compute in vq_iovecs().
|
||||
*
|
||||
* (I apologize for the two fields named idx; the
|
||||
* virtio spec calls the one that vue points to, "id"...)
|
||||
*/
|
||||
mask = vq->qsize - 1;
|
||||
vuh = vq->used;
|
||||
|
||||
uidx = vuh->idx;
|
||||
vue = &vuh->ring[uidx++ & mask];
|
||||
vue->idx = idx;
|
||||
vue->tlen = iolen;
|
||||
vuh->idx = uidx;
|
||||
}
|
||||
|
||||
/*
|
||||
* Driver has finished processing "available" chains and calling
|
||||
* vq_relchain on each one. If driver used all the available
|
||||
* chains, used_all should be set.
|
||||
*
|
||||
* If the "used" index moved we may need to inform the guest, i.e.,
|
||||
* deliver an interrupt. Even if the used index did NOT move we
|
||||
* may need to deliver an interrupt, if the avail ring is empty and
|
||||
* we are supposed to interrupt on empty.
|
||||
*
|
||||
* Note that used_all_avail is provided by the caller because it's
|
||||
* a snapshot of the ring state when he decided to finish interrupt
|
||||
* processing -- it's possible that descriptors became available after
|
||||
* that point. (It's also typically a constant 1/True as well.)
|
||||
*/
|
||||
void
|
||||
vq_endchains(struct virtio_vq_info *vq, int used_all_avail)
|
||||
{
|
||||
struct virtio_base *base;
|
||||
uint16_t event_idx, new_idx, old_idx;
|
||||
int intr;
|
||||
|
||||
/*
|
||||
* Interrupt generation: if we're using EVENT_IDX,
|
||||
* interrupt if we've crossed the event threshold.
|
||||
* Otherwise interrupt is generated if we added "used" entries,
|
||||
* but suppressed by VRING_AVAIL_F_NO_INTERRUPT.
|
||||
*
|
||||
* In any case, though, if NOTIFY_ON_EMPTY is set and the
|
||||
* entire avail was processed, we need to interrupt always.
|
||||
*/
|
||||
base = vq->base;
|
||||
old_idx = vq->save_used;
|
||||
vq->save_used = new_idx = vq->used->idx;
|
||||
if (used_all_avail &&
|
||||
(base->negotiated_caps & VIRTIO_F_NOTIFY_ON_EMPTY))
|
||||
intr = 1;
|
||||
else if (base->negotiated_caps & VIRTIO_RING_F_EVENT_IDX) {
|
||||
event_idx = VQ_USED_EVENT_IDX(vq);
|
||||
/*
|
||||
* This calculation is per docs and the kernel
|
||||
* (see src/sys/dev/virtio/virtio_ring.h).
|
||||
*/
|
||||
intr = (uint16_t)(new_idx - event_idx - 1) <
|
||||
(uint16_t)(new_idx - old_idx);
|
||||
} else {
|
||||
intr = new_idx != old_idx &&
|
||||
!(vq->avail->flags & VRING_AVAIL_F_NO_INTERRUPT);
|
||||
}
|
||||
if (intr)
|
||||
vq_interrupt(base, vq);
|
||||
}
|
||||
|
||||
/* Note: these are in sorted order to make for a fast search */
|
||||
static struct config_reg {
|
||||
uint16_t offset; /* register offset */
|
||||
uint8_t size; /* size (bytes) */
|
||||
uint8_t ro; /* true => reg is read only */
|
||||
const char *name; /* name of reg */
|
||||
} config_regs[] = {
|
||||
{ VIRTIO_CR_HOSTCAP, 4, 1, "HOSTCAP" },
|
||||
{ VIRTIO_CR_GUESTCAP, 4, 0, "GUESTCAP" },
|
||||
{ VIRTIO_CR_PFN, 4, 0, "PFN" },
|
||||
{ VIRTIO_CR_QNUM, 2, 1, "QNUM" },
|
||||
{ VIRTIO_CR_QSEL, 2, 0, "QSEL" },
|
||||
{ VIRTIO_CR_QNOTIFY, 2, 0, "QNOTIFY" },
|
||||
{ VIRTIO_CR_STATUS, 1, 0, "STATUS" },
|
||||
{ VIRTIO_CR_ISR, 1, 0, "ISR" },
|
||||
{ VIRTIO_CR_CFGVEC, 2, 0, "CFGVEC" },
|
||||
{ VIRTIO_CR_QVEC, 2, 0, "QVEC" },
|
||||
};
|
||||
|
||||
static inline struct config_reg *
|
||||
virtio_find_cr(int offset) {
|
||||
u_int hi, lo, mid;
|
||||
struct config_reg *cr;
|
||||
|
||||
lo = 0;
|
||||
hi = sizeof(config_regs) / sizeof(*config_regs) - 1;
|
||||
while (hi >= lo) {
|
||||
mid = (hi + lo) >> 1;
|
||||
cr = &config_regs[mid];
|
||||
if (cr->offset == offset)
|
||||
return cr;
|
||||
if (cr->offset < offset)
|
||||
lo = mid + 1;
|
||||
else
|
||||
hi = mid - 1;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle pci config space reads.
|
||||
* If it's to the MSI-X info, do that.
|
||||
* If it's part of the virtio standard stuff, do that.
|
||||
* Otherwise dispatch to the actual driver.
|
||||
*/
|
||||
uint64_t
|
||||
virtio_pci_read(struct vmctx *ctx, int vcpu, struct pci_vdev *dev,
|
||||
int baridx, uint64_t offset, int size)
|
||||
{
|
||||
struct virtio_base *base = dev->arg;
|
||||
struct virtio_ops *vops;
|
||||
struct config_reg *cr;
|
||||
uint64_t virtio_config_size, max;
|
||||
const char *name;
|
||||
uint32_t newoff;
|
||||
uint32_t value;
|
||||
int error;
|
||||
|
||||
if (base->flags & VIRTIO_USE_MSIX) {
|
||||
if (baridx == pci_msix_table_bar(dev) ||
|
||||
baridx == pci_msix_pba_bar(dev)) {
|
||||
return pci_emul_msix_tread(dev, offset, size);
|
||||
}
|
||||
}
|
||||
|
||||
/* XXX probably should do something better than just assert() */
|
||||
assert(baridx == 0);
|
||||
|
||||
if (base->mtx)
|
||||
pthread_mutex_lock(base->mtx);
|
||||
|
||||
vops = base->vops;
|
||||
name = vops->name;
|
||||
value = size == 1 ? 0xff : size == 2 ? 0xffff : 0xffffffff;
|
||||
|
||||
if (size != 1 && size != 2 && size != 4)
|
||||
goto bad;
|
||||
|
||||
if (pci_msix_enabled(dev))
|
||||
virtio_config_size = VIRTIO_CR_CFG1;
|
||||
else
|
||||
virtio_config_size = VIRTIO_CR_CFG0;
|
||||
|
||||
if (offset >= virtio_config_size) {
|
||||
/*
|
||||
* Subtract off the standard size (including MSI-X
|
||||
* registers if enabled) and dispatch to underlying driver.
|
||||
* If that fails, fall into general code.
|
||||
*/
|
||||
newoff = offset - virtio_config_size;
|
||||
max = vops->cfgsize ? vops->cfgsize : 0x100000000;
|
||||
if (newoff + size > max)
|
||||
goto bad;
|
||||
error = (*vops->cfgread)(DEV_STRUCT(base), newoff,
|
||||
size, &value);
|
||||
if (!error)
|
||||
goto done;
|
||||
}
|
||||
|
||||
bad:
|
||||
cr = virtio_find_cr(offset);
|
||||
if (cr == NULL || cr->size != size) {
|
||||
if (cr != NULL) {
|
||||
/* offset must be OK, so size must be bad */
|
||||
fprintf(stderr,
|
||||
"%s: read from %s: bad size %d\r\n",
|
||||
name, cr->name, size);
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"%s: read from bad offset/size %jd/%d\r\n",
|
||||
name, (uintmax_t)offset, size);
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
switch (offset) {
|
||||
case VIRTIO_CR_HOSTCAP:
|
||||
value = vops->hv_caps;
|
||||
break;
|
||||
case VIRTIO_CR_GUESTCAP:
|
||||
value = base->negotiated_caps;
|
||||
break;
|
||||
case VIRTIO_CR_PFN:
|
||||
if (base->curq < vops->nvq)
|
||||
value = base->queues[base->curq].pfn;
|
||||
break;
|
||||
case VIRTIO_CR_QNUM:
|
||||
value = base->curq < vops->nvq ?
|
||||
base->queues[base->curq].qsize : 0;
|
||||
break;
|
||||
case VIRTIO_CR_QSEL:
|
||||
value = base->curq;
|
||||
break;
|
||||
case VIRTIO_CR_QNOTIFY:
|
||||
value = 0; /* XXX */
|
||||
break;
|
||||
case VIRTIO_CR_STATUS:
|
||||
value = base->status;
|
||||
break;
|
||||
case VIRTIO_CR_ISR:
|
||||
value = base->isr;
|
||||
base->isr = 0; /* a read clears this flag */
|
||||
if (value)
|
||||
pci_lintr_deassert(dev);
|
||||
break;
|
||||
case VIRTIO_CR_CFGVEC:
|
||||
value = base->msix_cfg_idx;
|
||||
break;
|
||||
case VIRTIO_CR_QVEC:
|
||||
value = base->curq < vops->nvq ?
|
||||
base->queues[base->curq].msix_idx :
|
||||
VIRTIO_MSI_NO_VECTOR;
|
||||
break;
|
||||
}
|
||||
done:
|
||||
if (base->mtx)
|
||||
pthread_mutex_unlock(base->mtx);
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle pci config space writes.
|
||||
* If it's to the MSI-X info, do that.
|
||||
* If it's part of the virtio standard stuff, do that.
|
||||
* Otherwise dispatch to the actual driver.
|
||||
*/
|
||||
void
|
||||
virtio_pci_write(struct vmctx *ctx, int vcpu, struct pci_vdev *dev,
|
||||
int baridx, uint64_t offset, int size, uint64_t value)
|
||||
{
|
||||
struct virtio_base *base = dev->arg;
|
||||
struct virtio_vq_info *vq;
|
||||
struct virtio_ops *vops;
|
||||
struct config_reg *cr;
|
||||
uint64_t virtio_config_size, max;
|
||||
const char *name;
|
||||
uint32_t newoff;
|
||||
int error;
|
||||
|
||||
if (base->flags & VIRTIO_USE_MSIX) {
|
||||
if (baridx == pci_msix_table_bar(dev) ||
|
||||
baridx == pci_msix_pba_bar(dev)) {
|
||||
pci_emul_msix_twrite(dev, offset, size, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* XXX probably should do something better than just assert() */
|
||||
assert(baridx == 0);
|
||||
|
||||
if (base->mtx)
|
||||
pthread_mutex_lock(base->mtx);
|
||||
|
||||
vops = base->vops;
|
||||
name = vops->name;
|
||||
|
||||
if (size != 1 && size != 2 && size != 4)
|
||||
goto bad;
|
||||
|
||||
if (pci_msix_enabled(dev))
|
||||
virtio_config_size = VIRTIO_CR_CFG1;
|
||||
else
|
||||
virtio_config_size = VIRTIO_CR_CFG0;
|
||||
|
||||
if (offset >= virtio_config_size) {
|
||||
/*
|
||||
* Subtract off the standard size (including MSI-X
|
||||
* registers if enabled) and dispatch to underlying driver.
|
||||
*/
|
||||
newoff = offset - virtio_config_size;
|
||||
max = vops->cfgsize ? vops->cfgsize : 0x100000000;
|
||||
if (newoff + size > max)
|
||||
goto bad;
|
||||
error = (*vops->cfgwrite)(DEV_STRUCT(base), newoff,
|
||||
size, value);
|
||||
if (!error)
|
||||
goto done;
|
||||
}
|
||||
|
||||
bad:
|
||||
cr = virtio_find_cr(offset);
|
||||
if (cr == NULL || cr->size != size || cr->ro) {
|
||||
if (cr != NULL) {
|
||||
/* offset must be OK, wrong size and/or reg is R/O */
|
||||
if (cr->size != size)
|
||||
fprintf(stderr,
|
||||
"%s: write to %s: bad size %d\r\n",
|
||||
name, cr->name, size);
|
||||
if (cr->ro)
|
||||
fprintf(stderr,
|
||||
"%s: write to read-only reg %s\r\n",
|
||||
name, cr->name);
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"%s: write to bad offset/size %jd/%d\r\n",
|
||||
name, (uintmax_t)offset, size);
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
switch (offset) {
|
||||
case VIRTIO_CR_GUESTCAP:
|
||||
base->negotiated_caps = value & vops->hv_caps;
|
||||
if (vops->apply_features)
|
||||
(*vops->apply_features)(DEV_STRUCT(base),
|
||||
base->negotiated_caps);
|
||||
break;
|
||||
case VIRTIO_CR_PFN:
|
||||
if (base->curq >= vops->nvq)
|
||||
goto bad_qindex;
|
||||
virtio_vq_init(base, value);
|
||||
break;
|
||||
case VIRTIO_CR_QSEL:
|
||||
/*
|
||||
* Note that the guest is allowed to select an
|
||||
* invalid queue; we just need to return a QNUM
|
||||
* of 0 while the bad queue is selected.
|
||||
*/
|
||||
base->curq = value;
|
||||
break;
|
||||
case VIRTIO_CR_QNOTIFY:
|
||||
if (value >= vops->nvq) {
|
||||
fprintf(stderr, "%s: queue %d notify out of range\r\n",
|
||||
name, (int)value);
|
||||
goto done;
|
||||
}
|
||||
vq = &base->queues[value];
|
||||
if (vq->notify)
|
||||
(*vq->notify)(DEV_STRUCT(base), vq);
|
||||
else if (vops->qnotify)
|
||||
(*vops->qnotify)(DEV_STRUCT(base), vq);
|
||||
else
|
||||
fprintf(stderr,
|
||||
"%s: qnotify queue %d: missing vq/vops notify\r\n",
|
||||
name, (int)value);
|
||||
break;
|
||||
case VIRTIO_CR_STATUS:
|
||||
base->status = value;
|
||||
if (vops->set_status)
|
||||
(*vops->set_status)(DEV_STRUCT(base), value);
|
||||
if (value == 0)
|
||||
(*vops->reset)(DEV_STRUCT(base));
|
||||
break;
|
||||
case VIRTIO_CR_CFGVEC:
|
||||
base->msix_cfg_idx = value;
|
||||
break;
|
||||
case VIRTIO_CR_QVEC:
|
||||
if (base->curq >= vops->nvq)
|
||||
goto bad_qindex;
|
||||
vq = &base->queues[base->curq];
|
||||
vq->msix_idx = value;
|
||||
break;
|
||||
}
|
||||
goto done;
|
||||
|
||||
bad_qindex:
|
||||
fprintf(stderr,
|
||||
"%s: write config reg %s: curq %d >= max %d\r\n",
|
||||
name, cr->name, base->curq, vops->nvq);
|
||||
done:
|
||||
if (base->mtx)
|
||||
pthread_mutex_unlock(base->mtx);
|
||||
}
|
||||
441
devicemodel/hw/pci/virtio/virtio_block.c
Normal file
441
devicemodel/hw/pci/virtio/virtio_block.c
Normal file
@@ -0,0 +1,441 @@
|
||||
/*-
|
||||
* Copyright (c) 2011 NetApp, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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 NETAPP, INC ``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 NETAPP, INC 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.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <openssl/md5.h>
|
||||
|
||||
#include "dm.h"
|
||||
#include "pci_core.h"
|
||||
#include "virtio.h"
|
||||
#include "block_if.h"
|
||||
|
||||
#define VIRTIO_BLK_RINGSZ 64
|
||||
|
||||
#define VIRTIO_BLK_S_OK 0
|
||||
#define VIRTIO_BLK_S_IOERR 1
|
||||
#define VIRTIO_BLK_S_UNSUPP 2
|
||||
|
||||
#define VIRTIO_BLK_BLK_ID_BYTES 20
|
||||
|
||||
/* Capability bits */
|
||||
#define VIRTIO_BLK_F_SEG_MAX (1 << 2) /* Maximum request segments */
|
||||
#define VIRTIO_BLK_F_BLK_SIZE (1 << 6) /* cfg block size valid */
|
||||
#define VIRTIO_BLK_F_FLUSH (1 << 9) /* Cache flush support */
|
||||
#define VIRTIO_BLK_F_TOPOLOGY (1 << 10) /* Optimal I/O alignment */
|
||||
|
||||
/*
|
||||
* Host capabilities
|
||||
*/
|
||||
#define VIRTIO_BLK_S_HOSTCAPS \
|
||||
(VIRTIO_BLK_F_SEG_MAX | \
|
||||
VIRTIO_BLK_F_BLK_SIZE | \
|
||||
VIRTIO_BLK_F_FLUSH | \
|
||||
VIRTIO_BLK_F_TOPOLOGY | \
|
||||
VIRTIO_RING_F_INDIRECT_DESC) /* indirect descriptors */
|
||||
|
||||
/*
|
||||
* Config space "registers"
|
||||
*/
|
||||
struct virtio_blk_config {
|
||||
uint64_t capacity;
|
||||
uint32_t size_max;
|
||||
uint32_t seg_max;
|
||||
struct {
|
||||
uint16_t cylinders;
|
||||
uint8_t heads;
|
||||
uint8_t sectors;
|
||||
} geometry;
|
||||
uint32_t blk_size;
|
||||
struct {
|
||||
uint8_t physical_block_exp;
|
||||
uint8_t alignment_offset;
|
||||
uint16_t min_io_size;
|
||||
uint32_t opt_io_size;
|
||||
} topology;
|
||||
uint8_t writeback;
|
||||
} __attribute__((packed));
|
||||
|
||||
/*
|
||||
* Fixed-size block header
|
||||
*/
|
||||
struct virtio_blk_hdr {
|
||||
#define VBH_OP_READ 0
|
||||
#define VBH_OP_WRITE 1
|
||||
#define VBH_OP_FLUSH 4
|
||||
#define VBH_OP_FLUSH_OUT 5
|
||||
#define VBH_OP_IDENT 8
|
||||
#define VBH_FLAG_BARRIER 0x80000000 /* OR'ed into type */
|
||||
uint32_t type;
|
||||
uint32_t ioprio;
|
||||
uint64_t sector;
|
||||
} __attribute__((packed));
|
||||
|
||||
/*
|
||||
* Debug printf
|
||||
*/
|
||||
static int virtio_blk_debug;
|
||||
#define DPRINTF(params) do { if (virtio_blk_debug) printf params; } while (0)
|
||||
#define WPRINTF(params) (printf params)
|
||||
|
||||
struct virtio_blk_ioreq {
|
||||
struct blockif_req req;
|
||||
struct virtio_blk *blk;
|
||||
uint8_t *status;
|
||||
uint16_t idx;
|
||||
};
|
||||
|
||||
/*
|
||||
* Per-device struct
|
||||
*/
|
||||
struct virtio_blk {
|
||||
struct virtio_base base;
|
||||
pthread_mutex_t mtx;
|
||||
struct virtio_vq_info vq;
|
||||
struct virtio_blk_config cfg;
|
||||
struct blockif_ctxt *bc;
|
||||
char ident[VIRTIO_BLK_BLK_ID_BYTES + 1];
|
||||
struct virtio_blk_ioreq ios[VIRTIO_BLK_RINGSZ];
|
||||
};
|
||||
|
||||
static void virtio_blk_reset(void *);
|
||||
static void virtio_blk_notify(void *, struct virtio_vq_info *);
|
||||
static int virtio_blk_cfgread(void *, int, int, uint32_t *);
|
||||
static int virtio_blk_cfgwrite(void *, int, int, uint32_t);
|
||||
|
||||
static struct virtio_ops virtio_blk_ops = {
|
||||
"virtio_blk", /* our name */
|
||||
1, /* we support 1 virtqueue */
|
||||
sizeof(struct virtio_blk_config), /* config reg size */
|
||||
virtio_blk_reset, /* reset */
|
||||
virtio_blk_notify, /* device-wide qnotify */
|
||||
virtio_blk_cfgread, /* read PCI config */
|
||||
virtio_blk_cfgwrite, /* write PCI config */
|
||||
NULL, /* apply negotiated features */
|
||||
NULL, /* called on guest set status */
|
||||
VIRTIO_BLK_S_HOSTCAPS, /* our capabilities */
|
||||
};
|
||||
|
||||
static void
|
||||
virtio_blk_reset(void *vdev)
|
||||
{
|
||||
struct virtio_blk *blk = vdev;
|
||||
|
||||
DPRINTF(("virtio_blk: device reset requested !\n"));
|
||||
virtio_reset_dev(&blk->base);
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_blk_done(struct blockif_req *br, int err)
|
||||
{
|
||||
struct virtio_blk_ioreq *io = br->param;
|
||||
struct virtio_blk *blk = io->blk;
|
||||
|
||||
/* convert errno into a virtio block error return */
|
||||
if (err == EOPNOTSUPP || err == ENOSYS)
|
||||
*io->status = VIRTIO_BLK_S_UNSUPP;
|
||||
else if (err != 0)
|
||||
*io->status = VIRTIO_BLK_S_IOERR;
|
||||
else
|
||||
*io->status = VIRTIO_BLK_S_OK;
|
||||
|
||||
/*
|
||||
* Return the descriptor back to the host.
|
||||
* We wrote 1 byte (our status) to host.
|
||||
*/
|
||||
pthread_mutex_lock(&blk->mtx);
|
||||
vq_relchain(&blk->vq, io->idx, 1);
|
||||
vq_endchains(&blk->vq, 0);
|
||||
pthread_mutex_unlock(&blk->mtx);
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_blk_proc(struct virtio_blk *blk, struct virtio_vq_info *vq)
|
||||
{
|
||||
struct virtio_blk_hdr *vbh;
|
||||
struct virtio_blk_ioreq *io;
|
||||
int i, n;
|
||||
int err;
|
||||
ssize_t iolen;
|
||||
int writeop, type;
|
||||
struct iovec iov[BLOCKIF_IOV_MAX + 2];
|
||||
uint16_t idx, flags[BLOCKIF_IOV_MAX + 2];
|
||||
|
||||
n = vq_getchain(vq, &idx, iov, BLOCKIF_IOV_MAX + 2, flags);
|
||||
|
||||
/*
|
||||
* The first descriptor will be the read-only fixed header,
|
||||
* and the last is for status (hence +2 above and below).
|
||||
* The remaining iov's are the actual data I/O vectors.
|
||||
*
|
||||
* XXX - note - this fails on crash dump, which does a
|
||||
* VIRTIO_BLK_T_FLUSH with a zero transfer length
|
||||
*/
|
||||
assert(n >= 2 && n <= BLOCKIF_IOV_MAX + 2);
|
||||
|
||||
io = &blk->ios[idx];
|
||||
assert((flags[0] & VRING_DESC_F_WRITE) == 0);
|
||||
assert(iov[0].iov_len == sizeof(struct virtio_blk_hdr));
|
||||
vbh = iov[0].iov_base;
|
||||
memcpy(&io->req.iov, &iov[1], sizeof(struct iovec) * (n - 2));
|
||||
io->req.iovcnt = n - 2;
|
||||
io->req.offset = vbh->sector * DEV_BSIZE;
|
||||
io->status = iov[--n].iov_base;
|
||||
assert(iov[n].iov_len == 1);
|
||||
assert(flags[n] & VRING_DESC_F_WRITE);
|
||||
|
||||
/*
|
||||
* XXX
|
||||
* The guest should not be setting the BARRIER flag because
|
||||
* we don't advertise the capability.
|
||||
*/
|
||||
type = vbh->type & ~VBH_FLAG_BARRIER;
|
||||
writeop = (type == VBH_OP_WRITE);
|
||||
|
||||
iolen = 0;
|
||||
for (i = 1; i < n; i++) {
|
||||
/*
|
||||
* - write op implies read-only descriptor,
|
||||
* - read/ident op implies write-only descriptor,
|
||||
* therefore test the inverse of the descriptor bit
|
||||
* to the op.
|
||||
*/
|
||||
assert(((flags[i] & VRING_DESC_F_WRITE) == 0) == writeop);
|
||||
iolen += iov[i].iov_len;
|
||||
}
|
||||
io->req.resid = iolen;
|
||||
|
||||
DPRINTF(("virtio-block: %s op, %zd bytes, %d segs, offset %ld\n\r",
|
||||
writeop ? "write" : "read/ident", iolen, i - 1,
|
||||
io->req.offset));
|
||||
|
||||
switch (type) {
|
||||
case VBH_OP_READ:
|
||||
err = blockif_read(blk->bc, &io->req);
|
||||
break;
|
||||
case VBH_OP_WRITE:
|
||||
err = blockif_write(blk->bc, &io->req);
|
||||
break;
|
||||
case VBH_OP_FLUSH:
|
||||
case VBH_OP_FLUSH_OUT:
|
||||
err = blockif_flush(blk->bc, &io->req);
|
||||
break;
|
||||
case VBH_OP_IDENT:
|
||||
/* Assume a single buffer */
|
||||
/* S/n equal to buffer is not zero-terminated. */
|
||||
memset(iov[1].iov_base, 0, iov[1].iov_len);
|
||||
strncpy(iov[1].iov_base, blk->ident,
|
||||
MIN(iov[1].iov_len, sizeof(blk->ident)));
|
||||
virtio_blk_done(&io->req, 0);
|
||||
return;
|
||||
default:
|
||||
virtio_blk_done(&io->req, EOPNOTSUPP);
|
||||
return;
|
||||
}
|
||||
assert(err == 0);
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_blk_notify(void *vdev, struct virtio_vq_info *vq)
|
||||
{
|
||||
struct virtio_blk *blk = vdev;
|
||||
|
||||
while (vq_has_descs(vq))
|
||||
virtio_blk_proc(blk, vq);
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_blk_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
||||
{
|
||||
char bident[sizeof("XX:X:X")];
|
||||
struct blockif_ctxt *bctxt;
|
||||
MD5_CTX mdctx;
|
||||
u_char digest[16];
|
||||
struct virtio_blk *blk;
|
||||
off_t size;
|
||||
int i, sectsz, sts, sto;
|
||||
pthread_mutexattr_t attr;
|
||||
int rc;
|
||||
|
||||
if (opts == NULL) {
|
||||
printf("virtio-block: backing device required\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* The supplied backing file has to exist
|
||||
*/
|
||||
snprintf(bident, sizeof(bident), "%d:%d", dev->slot, dev->func);
|
||||
bctxt = blockif_open(opts, bident);
|
||||
if (bctxt == NULL) {
|
||||
perror("Could not open backing file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size = blockif_size(bctxt);
|
||||
sectsz = blockif_sectsz(bctxt);
|
||||
blockif_psectsz(bctxt, &sts, &sto);
|
||||
|
||||
blk = calloc(1, sizeof(struct virtio_blk));
|
||||
if (!blk) {
|
||||
WPRINTF(("virtio_blk: calloc returns NULL\n"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
blk->bc = bctxt;
|
||||
for (i = 0; i < VIRTIO_BLK_RINGSZ; i++) {
|
||||
struct virtio_blk_ioreq *io = &blk->ios[i];
|
||||
|
||||
io->req.callback = virtio_blk_done;
|
||||
io->req.param = io;
|
||||
io->blk = blk;
|
||||
io->idx = i;
|
||||
}
|
||||
|
||||
/* init mutex attribute properly to avoid deadlock */
|
||||
rc = pthread_mutexattr_init(&attr);
|
||||
if (rc)
|
||||
DPRINTF(("mutexattr init failed with erro %d!\n", rc));
|
||||
rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
if (rc)
|
||||
DPRINTF(("virtio_blk: mutexattr_settype failed with "
|
||||
"error %d!\n", rc));
|
||||
|
||||
rc = pthread_mutex_init(&blk->mtx, &attr);
|
||||
if (rc)
|
||||
DPRINTF(("virtio_blk: pthread_mutex_init failed with "
|
||||
"error %d!\n", rc));
|
||||
|
||||
/* init virtio struct and virtqueues */
|
||||
virtio_linkup(&blk->base, &virtio_blk_ops, blk, dev, &blk->vq);
|
||||
blk->base.mtx = &blk->mtx;
|
||||
|
||||
blk->vq.qsize = VIRTIO_BLK_RINGSZ;
|
||||
/* blk->vq.vq_notify = we have no per-queue notify */
|
||||
|
||||
/*
|
||||
* Create an identifier for the backing file. Use parts of the
|
||||
* md5 sum of the filename
|
||||
*/
|
||||
MD5_Init(&mdctx);
|
||||
MD5_Update(&mdctx, opts, strlen(opts));
|
||||
MD5_Final(digest, &mdctx);
|
||||
sprintf(blk->ident, "ACRN--%02X%02X-%02X%02X-%02X%02X",
|
||||
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5]);
|
||||
|
||||
/* setup virtio block config space */
|
||||
blk->cfg.capacity = size / DEV_BSIZE; /* 512-byte units */
|
||||
blk->cfg.size_max = 0; /* not negotiated */
|
||||
blk->cfg.seg_max = BLOCKIF_IOV_MAX;
|
||||
blk->cfg.geometry.cylinders = 0; /* no geometry */
|
||||
blk->cfg.geometry.heads = 0;
|
||||
blk->cfg.geometry.sectors = 0;
|
||||
blk->cfg.blk_size = sectsz;
|
||||
blk->cfg.topology.physical_block_exp =
|
||||
(sts > sectsz) ? (ffsll(sts / sectsz) - 1) : 0;
|
||||
blk->cfg.topology.alignment_offset =
|
||||
(sto != 0) ? ((sts - sto) / sectsz) : 0;
|
||||
blk->cfg.topology.min_io_size = 0;
|
||||
blk->cfg.topology.opt_io_size = 0;
|
||||
blk->cfg.writeback = 0;
|
||||
|
||||
/*
|
||||
* Should we move some of this into virtio.c? Could
|
||||
* have the device, class, and subdev_0 as fields in
|
||||
* the virtio constants structure.
|
||||
*/
|
||||
pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_BLOCK);
|
||||
pci_set_cfgdata16(dev, PCIR_VENDOR, VIRTIO_VENDOR);
|
||||
pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_STORAGE);
|
||||
pci_set_cfgdata16(dev, PCIR_SUBDEV_0, VIRTIO_TYPE_BLOCK);
|
||||
pci_set_cfgdata16(dev, PCIR_SUBVEND_0, VIRTIO_VENDOR);
|
||||
|
||||
if (virtio_interrupt_init(&blk->base, fbsdrun_virtio_msix())) {
|
||||
blockif_close(blk->bc);
|
||||
free(blk);
|
||||
return -1;
|
||||
}
|
||||
virtio_set_io_bar(&blk->base, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_blk_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
||||
{
|
||||
struct blockif_ctxt *bctxt;
|
||||
struct virtio_blk *blk;
|
||||
|
||||
if (dev->arg) {
|
||||
DPRINTF(("virtio_blk: deinit\n"));
|
||||
blk = (struct virtio_blk *) dev->arg;
|
||||
bctxt = blk->bc;
|
||||
blockif_close(bctxt);
|
||||
free(blk);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_blk_cfgwrite(void *vdev, int offset, int size, uint32_t value)
|
||||
{
|
||||
DPRINTF(("virtio_blk: write to readonly reg %d\n\r", offset));
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_blk_cfgread(void *vdev, int offset, int size, uint32_t *retval)
|
||||
{
|
||||
struct virtio_blk *blk = vdev;
|
||||
void *ptr;
|
||||
|
||||
/* our caller has already verified offset and size */
|
||||
ptr = (uint8_t *)&blk->cfg + offset;
|
||||
memcpy(retval, ptr, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct pci_vdev_ops pci_ops_virtio_blk = {
|
||||
.class_name = "virtio-blk",
|
||||
.vdev_init = virtio_blk_init,
|
||||
.vdev_deinit = virtio_blk_deinit,
|
||||
.vdev_barwrite = virtio_pci_write,
|
||||
.vdev_barread = virtio_pci_read
|
||||
};
|
||||
DEFINE_PCI_DEVTYPE(pci_ops_virtio_blk);
|
||||
937
devicemodel/hw/pci/virtio/virtio_console.c
Normal file
937
devicemodel/hw/pci/virtio/virtio_console.c
Normal file
@@ -0,0 +1,937 @@
|
||||
/*-
|
||||
* Copyright (c) 2016 iXsystems Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software was developed by Jakub Klama <jceel@FreeBSD.org>
|
||||
* under sponsorship from iXsystems Inc.
|
||||
*
|
||||
* 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
|
||||
* in this position and unchanged.
|
||||
* 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/cdefs.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <libgen.h>
|
||||
#include <sysexits.h>
|
||||
#include <termios.h>
|
||||
|
||||
#include "dm.h"
|
||||
#include "pci_core.h"
|
||||
#include "virtio.h"
|
||||
#include "mevent.h"
|
||||
|
||||
#define VIRTIO_CONSOLE_RINGSZ 64
|
||||
#define VIRTIO_CONSOLE_MAXPORTS 16
|
||||
#define VIRTIO_CONSOLE_MAXQ (VIRTIO_CONSOLE_MAXPORTS * 2 + 2)
|
||||
|
||||
#define VIRTIO_CONSOLE_DEVICE_READY 0
|
||||
#define VIRTIO_CONSOLE_DEVICE_ADD 1
|
||||
#define VIRTIO_CONSOLE_DEVICE_REMOVE 2
|
||||
#define VIRTIO_CONSOLE_PORT_READY 3
|
||||
#define VIRTIO_CONSOLE_CONSOLE_PORT 4
|
||||
#define VIRTIO_CONSOLE_CONSOLE_RESIZE 5
|
||||
#define VIRTIO_CONSOLE_PORT_OPEN 6
|
||||
#define VIRTIO_CONSOLE_PORT_NAME 7
|
||||
|
||||
#define VIRTIO_CONSOLE_F_SIZE 0
|
||||
#define VIRTIO_CONSOLE_F_MULTIPORT 1
|
||||
#define VIRTIO_CONSOLE_F_EMERG_WRITE 2
|
||||
#define VIRTIO_CONSOLE_S_HOSTCAPS \
|
||||
(VIRTIO_CONSOLE_F_SIZE | \
|
||||
VIRTIO_CONSOLE_F_MULTIPORT | \
|
||||
VIRTIO_CONSOLE_F_EMERG_WRITE)
|
||||
|
||||
static int virtio_console_debug;
|
||||
#define DPRINTF(params) do { \
|
||||
if (virtio_console_debug) \
|
||||
printf params; \
|
||||
} while (0)
|
||||
#define WPRINTF(params) (printf params)
|
||||
|
||||
struct virtio_console;
|
||||
struct virtio_console_port;
|
||||
struct virtio_console_config;
|
||||
typedef void (virtio_console_cb_t)(struct virtio_console_port *, void *,
|
||||
struct iovec *, int);
|
||||
|
||||
enum virtio_console_be_type {
|
||||
VIRTIO_CONSOLE_BE_STDIO = 0,
|
||||
VIRTIO_CONSOLE_BE_TTY,
|
||||
VIRTIO_CONSOLE_BE_PTY,
|
||||
VIRTIO_CONSOLE_BE_FILE,
|
||||
VIRTIO_CONSOLE_BE_MAX,
|
||||
VIRTIO_CONSOLE_BE_INVALID = VIRTIO_CONSOLE_BE_MAX
|
||||
};
|
||||
|
||||
struct virtio_console_port {
|
||||
struct virtio_console *console;
|
||||
int id;
|
||||
const char *name;
|
||||
bool enabled;
|
||||
bool is_console;
|
||||
bool rx_ready;
|
||||
bool open;
|
||||
int rxq;
|
||||
int txq;
|
||||
void *arg;
|
||||
virtio_console_cb_t *cb;
|
||||
};
|
||||
|
||||
struct virtio_console_backend {
|
||||
struct virtio_console_port *port;
|
||||
struct mevent *evp;
|
||||
int fd;
|
||||
bool open;
|
||||
enum virtio_console_be_type be_type;
|
||||
int pts_fd; /* only valid for PTY */
|
||||
};
|
||||
|
||||
struct virtio_console {
|
||||
struct virtio_base base;
|
||||
struct virtio_vq_info queues[VIRTIO_CONSOLE_MAXQ];
|
||||
pthread_mutex_t mtx;
|
||||
uint64_t cfg;
|
||||
uint64_t features;
|
||||
int nports;
|
||||
bool ready;
|
||||
struct virtio_console_port control_port;
|
||||
struct virtio_console_port ports[VIRTIO_CONSOLE_MAXPORTS];
|
||||
struct virtio_console_config *config;
|
||||
};
|
||||
|
||||
struct virtio_console_config {
|
||||
uint16_t cols;
|
||||
uint16_t rows;
|
||||
uint32_t max_nr_ports;
|
||||
uint32_t emerg_wr;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_console_control {
|
||||
uint32_t id;
|
||||
uint16_t event;
|
||||
uint16_t value;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_console_console_resize {
|
||||
uint16_t cols;
|
||||
uint16_t rows;
|
||||
} __attribute__((packed));
|
||||
|
||||
static void virtio_console_reset(void *);
|
||||
static void virtio_console_notify_rx(void *, struct virtio_vq_info *);
|
||||
static void virtio_console_notify_tx(void *, struct virtio_vq_info *);
|
||||
static int virtio_console_cfgread(void *, int, int, uint32_t *);
|
||||
static int virtio_console_cfgwrite(void *, int, int, uint32_t);
|
||||
static void virtio_console_neg_features(void *, uint64_t);
|
||||
static void virtio_console_control_send(struct virtio_console *,
|
||||
struct virtio_console_control *, const void *, size_t);
|
||||
static void virtio_console_announce_port(struct virtio_console_port *);
|
||||
static void virtio_console_open_port(struct virtio_console_port *, bool);
|
||||
|
||||
static struct virtio_ops virtio_console_ops = {
|
||||
"vtcon", /* our name */
|
||||
VIRTIO_CONSOLE_MAXQ, /* we support VTCON_MAXQ virtqueues */
|
||||
sizeof(struct virtio_console_config), /* config reg size */
|
||||
virtio_console_reset, /* reset */
|
||||
NULL, /* device-wide qnotify */
|
||||
virtio_console_cfgread, /* read virtio config */
|
||||
virtio_console_cfgwrite, /* write virtio config */
|
||||
virtio_console_neg_features, /* apply negotiated features */
|
||||
NULL, /* called on guest set status */
|
||||
VIRTIO_CONSOLE_S_HOSTCAPS, /* our capabilities */
|
||||
};
|
||||
|
||||
static const char *virtio_console_be_table[VIRTIO_CONSOLE_BE_MAX] = {
|
||||
[VIRTIO_CONSOLE_BE_STDIO] = "stdio",
|
||||
[VIRTIO_CONSOLE_BE_TTY] = "tty",
|
||||
[VIRTIO_CONSOLE_BE_PTY] = "pty",
|
||||
[VIRTIO_CONSOLE_BE_FILE] = "file"
|
||||
};
|
||||
|
||||
static struct termios virtio_console_saved_tio;
|
||||
static int virtio_console_saved_flags;
|
||||
|
||||
static void
|
||||
virtio_console_reset(void *vdev)
|
||||
{
|
||||
struct virtio_console *console;
|
||||
|
||||
console = vdev;
|
||||
|
||||
DPRINTF(("vtcon: device reset requested!\n"));
|
||||
virtio_reset_dev(&console->base);
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_neg_features(void *vdev, uint64_t negotiated_features)
|
||||
{
|
||||
struct virtio_console *console = vdev;
|
||||
|
||||
console->features = negotiated_features;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_console_cfgread(void *vdev, int offset, int size, uint32_t *retval)
|
||||
{
|
||||
struct virtio_console *console = vdev;
|
||||
void *ptr;
|
||||
|
||||
ptr = (uint8_t *)console->config + offset;
|
||||
memcpy(retval, ptr, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_console_cfgwrite(void *vdev, int offset, int size, uint32_t val)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline struct virtio_console_port *
|
||||
virtio_console_vq_to_port(struct virtio_console *console,
|
||||
struct virtio_vq_info *vq)
|
||||
{
|
||||
uint16_t num = vq->num;
|
||||
|
||||
if (num == 0 || num == 1)
|
||||
return &console->ports[0];
|
||||
|
||||
if (num == 2 || num == 3)
|
||||
return &console->control_port;
|
||||
|
||||
return &console->ports[(num / 2) - 1];
|
||||
}
|
||||
|
||||
static inline struct virtio_vq_info *
|
||||
virtio_console_port_to_vq(struct virtio_console_port *port, bool tx_queue)
|
||||
{
|
||||
int qnum;
|
||||
|
||||
qnum = tx_queue ? port->txq : port->rxq;
|
||||
return &port->console->queues[qnum];
|
||||
}
|
||||
|
||||
static struct virtio_console_port *
|
||||
virtio_console_add_port(struct virtio_console *console, const char *name,
|
||||
virtio_console_cb_t *cb, void *arg, bool is_console)
|
||||
{
|
||||
struct virtio_console_port *port;
|
||||
|
||||
if (console->nports == VIRTIO_CONSOLE_MAXPORTS) {
|
||||
errno = EBUSY;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
port = &console->ports[console->nports++];
|
||||
port->id = console->nports - 1;
|
||||
port->console = console;
|
||||
port->name = name;
|
||||
port->cb = cb;
|
||||
port->arg = arg;
|
||||
port->is_console = is_console;
|
||||
|
||||
if (port->id == 0) {
|
||||
/* port0 */
|
||||
port->txq = 0;
|
||||
port->rxq = 1;
|
||||
} else {
|
||||
port->txq = console->nports * 2;
|
||||
port->rxq = port->txq + 1;
|
||||
}
|
||||
|
||||
port->enabled = true;
|
||||
return port;
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_control_tx(struct virtio_console_port *port, void *arg,
|
||||
struct iovec *iov, int niov)
|
||||
{
|
||||
struct virtio_console *console;
|
||||
struct virtio_console_port *tmp;
|
||||
struct virtio_console_control resp, *ctrl;
|
||||
int i;
|
||||
|
||||
assert(niov == 1);
|
||||
|
||||
console = port->console;
|
||||
ctrl = (struct virtio_console_control *)iov->iov_base;
|
||||
|
||||
switch (ctrl->event) {
|
||||
case VIRTIO_CONSOLE_DEVICE_READY:
|
||||
console->ready = true;
|
||||
/* set port ready events for registered ports */
|
||||
for (i = 0; i < VIRTIO_CONSOLE_MAXPORTS; i++) {
|
||||
tmp = &console->ports[i];
|
||||
if (tmp->enabled)
|
||||
virtio_console_announce_port(tmp);
|
||||
|
||||
if (tmp->open)
|
||||
virtio_console_open_port(tmp, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case VIRTIO_CONSOLE_PORT_READY:
|
||||
if (ctrl->id >= console->nports) {
|
||||
WPRINTF(("VTCONSOLE_PORT_READY for unknown port %d\n",
|
||||
ctrl->id));
|
||||
return;
|
||||
}
|
||||
|
||||
tmp = &console->ports[ctrl->id];
|
||||
if (tmp->is_console) {
|
||||
resp.event = VIRTIO_CONSOLE_CONSOLE_PORT;
|
||||
resp.id = ctrl->id;
|
||||
resp.value = 1;
|
||||
virtio_console_control_send(console, &resp, NULL, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_announce_port(struct virtio_console_port *port)
|
||||
{
|
||||
struct virtio_console_control event;
|
||||
|
||||
event.id = port->id;
|
||||
event.event = VIRTIO_CONSOLE_DEVICE_ADD;
|
||||
event.value = 1;
|
||||
virtio_console_control_send(port->console, &event, NULL, 0);
|
||||
|
||||
event.event = VIRTIO_CONSOLE_PORT_NAME;
|
||||
virtio_console_control_send(port->console, &event, port->name,
|
||||
strlen(port->name));
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_open_port(struct virtio_console_port *port, bool open)
|
||||
{
|
||||
struct virtio_console_control event;
|
||||
|
||||
if (!port->console->ready) {
|
||||
port->open = true;
|
||||
return;
|
||||
}
|
||||
|
||||
event.id = port->id;
|
||||
event.event = VIRTIO_CONSOLE_PORT_OPEN;
|
||||
event.value = (int)open;
|
||||
virtio_console_control_send(port->console, &event, NULL, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_control_send(struct virtio_console *console,
|
||||
struct virtio_console_control *ctrl,
|
||||
const void *payload, size_t len)
|
||||
{
|
||||
struct virtio_vq_info *vq;
|
||||
struct iovec iov;
|
||||
uint16_t idx;
|
||||
int n;
|
||||
|
||||
vq = virtio_console_port_to_vq(&console->control_port, true);
|
||||
|
||||
if (!vq_has_descs(vq))
|
||||
return;
|
||||
|
||||
n = vq_getchain(vq, &idx, &iov, 1, NULL);
|
||||
|
||||
assert(n == 1);
|
||||
|
||||
memcpy(iov.iov_base, ctrl, sizeof(struct virtio_console_control));
|
||||
if (payload != NULL && len > 0)
|
||||
memcpy(iov.iov_base + sizeof(struct virtio_console_control),
|
||||
payload, len);
|
||||
|
||||
vq_relchain(vq, idx, sizeof(struct virtio_console_control) + len);
|
||||
vq_endchains(vq, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_notify_tx(void *vdev, struct virtio_vq_info *vq)
|
||||
{
|
||||
struct virtio_console *console;
|
||||
struct virtio_console_port *port;
|
||||
struct iovec iov[1];
|
||||
uint16_t idx;
|
||||
uint16_t flags[8];
|
||||
|
||||
console = vdev;
|
||||
port = virtio_console_vq_to_port(console, vq);
|
||||
|
||||
while (vq_has_descs(vq)) {
|
||||
vq_getchain(vq, &idx, iov, 1, flags);
|
||||
if (port != NULL)
|
||||
port->cb(port, port->arg, iov, 1);
|
||||
|
||||
/*
|
||||
* Release this chain and handle more
|
||||
*/
|
||||
vq_relchain(vq, idx, 0);
|
||||
}
|
||||
vq_endchains(vq, 1); /* Generate interrupt if appropriate. */
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_notify_rx(void *vdev, struct virtio_vq_info *vq)
|
||||
{
|
||||
struct virtio_console *console;
|
||||
struct virtio_console_port *port;
|
||||
|
||||
console = vdev;
|
||||
port = virtio_console_vq_to_port(console, vq);
|
||||
|
||||
if (!port->rx_ready) {
|
||||
port->rx_ready = 1;
|
||||
vq->used->flags |= VRING_USED_F_NO_NOTIFY;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_reset_backend(struct virtio_console_backend *be)
|
||||
{
|
||||
if (!be)
|
||||
return;
|
||||
|
||||
if (be->fd != STDIN_FILENO)
|
||||
mevent_delete_close(be->evp);
|
||||
else
|
||||
mevent_delete(be->evp);
|
||||
|
||||
if (be->be_type == VIRTIO_CONSOLE_BE_PTY && be->pts_fd > 0) {
|
||||
close(be->pts_fd);
|
||||
be->pts_fd = -1;
|
||||
}
|
||||
|
||||
be->evp = NULL;
|
||||
be->fd = -1;
|
||||
be->open = false;
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_backend_read(int fd __attribute__((unused)),
|
||||
enum ev_type t __attribute__((unused)),
|
||||
void *arg)
|
||||
{
|
||||
struct virtio_console_port *port;
|
||||
struct virtio_console_backend *be = arg;
|
||||
struct virtio_vq_info *vq;
|
||||
struct iovec iov;
|
||||
static char dummybuf[2048];
|
||||
int len, n;
|
||||
uint16_t idx;
|
||||
|
||||
port = be->port;
|
||||
vq = virtio_console_port_to_vq(port, true);
|
||||
|
||||
if (!be->open || !port->rx_ready) {
|
||||
len = read(be->fd, dummybuf, sizeof(dummybuf));
|
||||
if (len == 0)
|
||||
goto close;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vq_has_descs(vq)) {
|
||||
len = read(be->fd, dummybuf, sizeof(dummybuf));
|
||||
vq_endchains(vq, 1);
|
||||
if (len == 0)
|
||||
goto close;
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
n = vq_getchain(vq, &idx, &iov, 1, NULL);
|
||||
len = readv(be->fd, &iov, n);
|
||||
if (len <= 0) {
|
||||
vq_retchain(vq);
|
||||
vq_endchains(vq, 0);
|
||||
|
||||
/* no data available */
|
||||
if (len == -1 && errno == EAGAIN)
|
||||
return;
|
||||
|
||||
/* any other errors */
|
||||
goto close;
|
||||
}
|
||||
|
||||
vq_relchain(vq, idx, len);
|
||||
} while (vq_has_descs(vq));
|
||||
|
||||
vq_endchains(vq, 1);
|
||||
|
||||
close:
|
||||
virtio_console_reset_backend(be);
|
||||
WPRINTF(("vtcon: be read failed and close! len = %d, errno = %d\n",
|
||||
len, errno));
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_backend_write(struct virtio_console_port *port, void *arg,
|
||||
struct iovec *iov, int niov)
|
||||
{
|
||||
struct virtio_console_backend *be;
|
||||
int ret;
|
||||
|
||||
be = arg;
|
||||
|
||||
if (be->fd == -1)
|
||||
return;
|
||||
|
||||
ret = writev(be->fd, iov, niov);
|
||||
if (ret <= 0) {
|
||||
/* backend cannot receive more data. For example when pts is
|
||||
* not connected to any client, its tty buffer will become full.
|
||||
* In this case we just drop data from guest hvc console.
|
||||
*/
|
||||
if (ret == -1 && errno == EAGAIN)
|
||||
return;
|
||||
|
||||
virtio_console_reset_backend(be);
|
||||
WPRINTF(("vtcon: be write failed! errno = %d\n", errno));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_restore_stdio(void)
|
||||
{
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &virtio_console_saved_tio);
|
||||
fcntl(STDIN_FILENO, F_SETFL, virtio_console_saved_flags);
|
||||
stdio_in_use = false;
|
||||
}
|
||||
|
||||
static bool
|
||||
virtio_console_backend_can_read(enum virtio_console_be_type be_type)
|
||||
{
|
||||
return (be_type == VIRTIO_CONSOLE_BE_FILE) ? false : true;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_console_open_backend(const char *path,
|
||||
enum virtio_console_be_type be_type)
|
||||
{
|
||||
int fd = -1;
|
||||
|
||||
switch (be_type) {
|
||||
case VIRTIO_CONSOLE_BE_PTY:
|
||||
fd = posix_openpt(O_RDWR | O_NOCTTY);
|
||||
if (fd == -1)
|
||||
WPRINTF(("vtcon: posix_openpt failed, errno = %d\n",
|
||||
errno));
|
||||
else if (grantpt(fd) == -1 || unlockpt(fd) == -1) {
|
||||
WPRINTF(("vtcon: grant/unlock failed, errno = %d\n",
|
||||
errno));
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
break;
|
||||
case VIRTIO_CONSOLE_BE_STDIO:
|
||||
if (stdio_in_use) {
|
||||
WPRINTF(("vtcon: stdio is used by other device\n"));
|
||||
break;
|
||||
}
|
||||
fd = STDIN_FILENO;
|
||||
stdio_in_use = true;
|
||||
break;
|
||||
case VIRTIO_CONSOLE_BE_TTY:
|
||||
fd = open(path, O_RDWR | O_NONBLOCK);
|
||||
if (fd < 0)
|
||||
WPRINTF(("vtcon: open failed: %s\n", path));
|
||||
else if (!isatty(fd)) {
|
||||
WPRINTF(("vtcon: not a tty: %s\n", path));
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
break;
|
||||
case VIRTIO_CONSOLE_BE_FILE:
|
||||
fd = open(path, O_WRONLY|O_CREAT|O_APPEND|O_NONBLOCK, 0666);
|
||||
if (fd < 0)
|
||||
WPRINTF(("vtcon: open failed: %s\n", path));
|
||||
break;
|
||||
default:
|
||||
WPRINTF(("not supported backend %d!\n", be_type));
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_console_config_backend(struct virtio_console_backend *be)
|
||||
{
|
||||
int fd, flags;
|
||||
char *pts_name = NULL;
|
||||
int slave_fd = -1;
|
||||
struct termios tio, saved_tio;
|
||||
|
||||
if (!be || be->fd == -1)
|
||||
return -1;
|
||||
|
||||
fd = be->fd;
|
||||
switch (be->be_type) {
|
||||
case VIRTIO_CONSOLE_BE_PTY:
|
||||
pts_name = ptsname(fd);
|
||||
if (pts_name == NULL) {
|
||||
WPRINTF(("vtcon: ptsname return NULL, errno = %d\n",
|
||||
errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
slave_fd = open(pts_name, O_RDWR);
|
||||
if (slave_fd == -1) {
|
||||
WPRINTF(("vtcon: slave_fd open failed, errno = %d\n",
|
||||
errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
tcgetattr(slave_fd, &tio);
|
||||
cfmakeraw(&tio);
|
||||
tcsetattr(slave_fd, TCSAFLUSH, &tio);
|
||||
be->pts_fd = slave_fd;
|
||||
|
||||
WPRINTF(("***********************************************\n"));
|
||||
WPRINTF(("virt-console backend redirected to %s\n", pts_name));
|
||||
WPRINTF(("***********************************************\n"));
|
||||
|
||||
flags = fcntl(fd, F_GETFL);
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
break;
|
||||
case VIRTIO_CONSOLE_BE_TTY:
|
||||
case VIRTIO_CONSOLE_BE_STDIO:
|
||||
tcgetattr(fd, &tio);
|
||||
saved_tio = tio;
|
||||
cfmakeraw(&tio);
|
||||
tio.c_cflag |= CLOCAL;
|
||||
tcsetattr(fd, TCSANOW, &tio);
|
||||
|
||||
if (be->be_type == VIRTIO_CONSOLE_BE_STDIO) {
|
||||
flags = fcntl(fd, F_GETFL);
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
virtio_console_saved_flags = flags;
|
||||
virtio_console_saved_tio = saved_tio;
|
||||
atexit(virtio_console_restore_stdio);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break; /* nothing to do */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_console_add_backend(struct virtio_console *console,
|
||||
const char *name, const char *path,
|
||||
enum virtio_console_be_type be_type,
|
||||
bool is_console)
|
||||
{
|
||||
struct virtio_console_backend *be;
|
||||
int error = 0, fd = -1;
|
||||
|
||||
be = calloc(1, sizeof(struct virtio_console_backend));
|
||||
if (be == NULL) {
|
||||
error = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
fd = virtio_console_open_backend(path, be_type);
|
||||
if (fd < 0) {
|
||||
error = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
be->fd = fd;
|
||||
be->be_type = be_type;
|
||||
|
||||
if (virtio_console_config_backend(be) < 0) {
|
||||
WPRINTF(("vtcon: virtio_console_config_backend failed\n"));
|
||||
error = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
be->port = virtio_console_add_port(console, name,
|
||||
virtio_console_backend_write, be, is_console);
|
||||
if (be->port == NULL) {
|
||||
WPRINTF(("vtcon: virtio_console_add_port failed\n"));
|
||||
error = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (virtio_console_backend_can_read(be_type)) {
|
||||
be->evp = mevent_add(fd, EVF_READ,
|
||||
virtio_console_backend_read, be);
|
||||
if (be->evp == NULL) {
|
||||
WPRINTF(("vtcon: mevent_add failed\n"));
|
||||
error = -1;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
virtio_console_open_port(be->port, true);
|
||||
be->open = true;
|
||||
|
||||
out:
|
||||
if (error != 0) {
|
||||
if (be) {
|
||||
if (be->evp)
|
||||
mevent_delete(be->evp);
|
||||
if (be->port) {
|
||||
be->port->enabled = false;
|
||||
be->port->arg = NULL;
|
||||
}
|
||||
if (be->be_type == VIRTIO_CONSOLE_BE_PTY &&
|
||||
be->pts_fd > 0)
|
||||
close(be->pts_fd);
|
||||
free(be);
|
||||
}
|
||||
if (fd != -1 && fd != STDIN_FILENO)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_close_backend(struct virtio_console_backend *be)
|
||||
{
|
||||
if (!be)
|
||||
return;
|
||||
|
||||
switch (be->be_type) {
|
||||
case VIRTIO_CONSOLE_BE_PTY:
|
||||
if (be->pts_fd > 0) {
|
||||
close(be->pts_fd);
|
||||
be->pts_fd = -1;
|
||||
}
|
||||
break;
|
||||
case VIRTIO_CONSOLE_BE_STDIO:
|
||||
virtio_console_restore_stdio();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
be->fd = -1;
|
||||
be->open = false;
|
||||
memset(be->port, 0, sizeof(*be->port));
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_close_all(struct virtio_console *console)
|
||||
{
|
||||
int i;
|
||||
struct virtio_console_port *port;
|
||||
struct virtio_console_backend *be;
|
||||
|
||||
for (i = 0; i < console->nports; i++) {
|
||||
port = &console->ports[i];
|
||||
|
||||
if (!port->enabled)
|
||||
continue;
|
||||
|
||||
be = (struct virtio_console_backend *)port->arg;
|
||||
if (be) {
|
||||
virtio_console_close_backend(be);
|
||||
free(be);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static enum virtio_console_be_type
|
||||
virtio_console_get_be_type(const char *backend)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < VIRTIO_CONSOLE_BE_MAX; i++)
|
||||
if (strcasecmp(backend, virtio_console_be_table[i]) == 0)
|
||||
return i;
|
||||
|
||||
return VIRTIO_CONSOLE_BE_INVALID;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_console_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
||||
{
|
||||
struct virtio_console *console;
|
||||
char *backend = NULL;
|
||||
char *portname = NULL;
|
||||
char *portpath = NULL;
|
||||
char *opt;
|
||||
int i;
|
||||
pthread_mutexattr_t attr;
|
||||
enum virtio_console_be_type be_type;
|
||||
bool is_console = false;
|
||||
int rc;
|
||||
|
||||
if (!opts) {
|
||||
WPRINTF(("vtcon: invalid opts\n"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
console = calloc(1, sizeof(struct virtio_console));
|
||||
if (!console) {
|
||||
WPRINTF(("vtcon: calloc returns NULL\n"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
console->config = calloc(1, sizeof(struct virtio_console_config));
|
||||
if (!console->config) {
|
||||
WPRINTF(("vtcon->config: calloc returns NULL\n"));
|
||||
free(console);
|
||||
return -1;
|
||||
}
|
||||
|
||||
console->config->max_nr_ports = VIRTIO_CONSOLE_MAXPORTS;
|
||||
console->config->cols = 80;
|
||||
console->config->rows = 25;
|
||||
|
||||
/* init mutex attribute properly to avoid deadlock */
|
||||
rc = pthread_mutexattr_init(&attr);
|
||||
if (rc)
|
||||
DPRINTF(("mutexattr init failed with erro %d!\n", rc));
|
||||
rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
if (rc)
|
||||
DPRINTF(("virtio_console: mutexattr_settype failed with "
|
||||
"error %d!\n", rc));
|
||||
|
||||
rc = pthread_mutex_init(&console->mtx, &attr);
|
||||
if (rc)
|
||||
DPRINTF(("virtio_console: pthread_mutex_init failed with "
|
||||
"error %d!\n", rc));
|
||||
|
||||
virtio_linkup(&console->base, &virtio_console_ops, console, dev,
|
||||
console->queues);
|
||||
console->base.mtx = &console->mtx;
|
||||
|
||||
for (i = 0; i < VIRTIO_CONSOLE_MAXQ; i++) {
|
||||
console->queues[i].qsize = VIRTIO_CONSOLE_RINGSZ;
|
||||
console->queues[i].notify = i % 2 == 0
|
||||
? virtio_console_notify_rx
|
||||
: virtio_console_notify_tx;
|
||||
}
|
||||
|
||||
/* initialize config space */
|
||||
pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_CONSOLE);
|
||||
pci_set_cfgdata16(dev, PCIR_VENDOR, VIRTIO_VENDOR);
|
||||
pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_SIMPLECOMM);
|
||||
pci_set_cfgdata16(dev, PCIR_SUBDEV_0, VIRTIO_TYPE_CONSOLE);
|
||||
pci_set_cfgdata16(dev, PCIR_SUBVEND_0, VIRTIO_VENDOR);
|
||||
|
||||
if (virtio_interrupt_init(&console->base, fbsdrun_virtio_msix())) {
|
||||
if (console) {
|
||||
if (console->config)
|
||||
free(console->config);
|
||||
free(console);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
virtio_set_io_bar(&console->base, 0);
|
||||
|
||||
/* create control port */
|
||||
console->control_port.console = console;
|
||||
console->control_port.txq = 2;
|
||||
console->control_port.rxq = 3;
|
||||
console->control_port.cb = virtio_console_control_tx;
|
||||
console->control_port.enabled = true;
|
||||
|
||||
/* virtio-console,[@]stdio|tty|pty|file:portname[=portpath]
|
||||
* [,[@]stdio|tty|pty|file:portname[=portpath]]
|
||||
*/
|
||||
while ((opt = strsep(&opts, ",")) != NULL) {
|
||||
backend = strsep(&opt, ":");
|
||||
|
||||
if (backend == NULL) {
|
||||
WPRINTF(("vtcon: no backend is specified!\n"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (backend[0] == '@') {
|
||||
is_console = true;
|
||||
backend++;
|
||||
}
|
||||
|
||||
be_type = virtio_console_get_be_type(backend);
|
||||
if (be_type == VIRTIO_CONSOLE_BE_INVALID) {
|
||||
WPRINTF(("vtcon: invalid backend %s!\n",
|
||||
backend));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (opt != NULL) {
|
||||
portname = strsep(&opt, "=");
|
||||
portpath = opt;
|
||||
if (portpath == NULL
|
||||
&& be_type != VIRTIO_CONSOLE_BE_STDIO
|
||||
&& be_type != VIRTIO_CONSOLE_BE_PTY) {
|
||||
WPRINTF(("vtcon: portpath missing for %s\n",
|
||||
portname));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (virtio_console_add_backend(console, portname,
|
||||
portpath, be_type, is_console) < 0) {
|
||||
WPRINTF(("vtcon: add port failed %s\n",
|
||||
portname));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_console_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
||||
{
|
||||
struct virtio_console *console;
|
||||
|
||||
console = (struct virtio_console *)dev->arg;
|
||||
if (console) {
|
||||
virtio_console_close_all(console);
|
||||
if (console->config)
|
||||
free(console->config);
|
||||
free(console);
|
||||
}
|
||||
}
|
||||
|
||||
struct pci_vdev_ops pci_ops_virtio_console = {
|
||||
.class_name = "virtio-console",
|
||||
.vdev_init = virtio_console_init,
|
||||
.vdev_deinit = virtio_console_deinit,
|
||||
.vdev_barwrite = virtio_pci_write,
|
||||
.vdev_barread = virtio_pci_read
|
||||
};
|
||||
DEFINE_PCI_DEVTYPE(pci_ops_virtio_console);
|
||||
385
devicemodel/hw/pci/virtio/virtio_hyper_dmabuf.c
Normal file
385
devicemodel/hw/pci/virtio/virtio_hyper_dmabuf.c
Normal file
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Intel Corporation. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * 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.
|
||||
* * Neither the name of Intel Corporation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
|
||||
* OWNER 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* virtio hyper dmabuf
|
||||
* Allows to share data buffers between VMs using dmabuf like interface
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "dm.h"
|
||||
#include "pci_core.h"
|
||||
#include "virtio.h"
|
||||
#include "virtio_kernel.h"
|
||||
#include "vmmapi.h"
|
||||
|
||||
/*
|
||||
* Size of queue was chosen experimentaly in a way
|
||||
* that it allows to run ~20 shared surfaces without
|
||||
* any delays on hyper dmabuf dirver side due to lack
|
||||
* of free buffers in queue
|
||||
*/
|
||||
#define HYPER_DMABUF_RINGSZ 128
|
||||
|
||||
/* Hyper dmabuf uses two queues one for Rx and one for Tx */
|
||||
#define HYPER_DMABUF_VQ_NUM 2
|
||||
|
||||
const char *hyper_dmabuf_vbs_dev_path = "/dev/vbs_hyper_dmabuf";
|
||||
|
||||
static int virtio_hyper_dmabuf_debug;
|
||||
#define DPRINTF(...)\
|
||||
do {\
|
||||
if (virtio_hyper_dmabuf_debug)\
|
||||
printf(__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define WPRINTF(...) printf(__VA_ARGS__)
|
||||
|
||||
static enum VBS_K_STATUS kstatus = VIRTIO_DEV_INITIAL;
|
||||
static int vbs_k_hyper_dmabuf_fd = -1;
|
||||
static struct vbs_dev_info kdev;
|
||||
static struct vbs_vqs_info kvqs;
|
||||
|
||||
struct virtio_hyper_dmabuf {
|
||||
struct virtio_base base;
|
||||
struct virtio_vq_info vq[HYPER_DMABUF_VQ_NUM];
|
||||
pthread_mutex_t mtx;
|
||||
};
|
||||
|
||||
static int virtio_hyper_dmabuf_k_init(void);
|
||||
static int virtio_hyper_dmabuf_k_start(void);
|
||||
static int virtio_hyper_dmabuf_k_stop(void);
|
||||
static int virtio_hyper_dmabuf_k_reset(void);
|
||||
static int virtio_hyper_dmabuf_k_dev_set(const char *name, int vmid,
|
||||
int nvq, uint32_t feature,
|
||||
uint64_t pio_start, uint64_t pio_len);
|
||||
|
||||
static int virtio_hyper_dmabuf_k_vq_set(unsigned int nvq, unsigned int idx,
|
||||
uint16_t qsize,
|
||||
uint32_t pfn, uint16_t msix_idx,
|
||||
uint64_t msix_addr, uint32_t msix_data);
|
||||
|
||||
static void virtio_hyper_dmabuf_no_notify(void *, struct virtio_vq_info *);
|
||||
static void virtio_hyper_dmabuf_set_status(void *, uint64_t);
|
||||
static void virtio_hyper_dmabuf_reset(void *);
|
||||
|
||||
static struct virtio_ops virtio_hyper_dmabuf_ops_k = {
|
||||
"virtio_hyper_dmabuf", /* our name */
|
||||
HYPER_DMABUF_VQ_NUM, /* we support 2 virtqueue */
|
||||
0, /* config reg size */
|
||||
virtio_hyper_dmabuf_reset, /* reset */
|
||||
virtio_hyper_dmabuf_no_notify, /* device-wide qnotify */
|
||||
NULL, /* read virtio config */
|
||||
NULL, /* write virtio config */
|
||||
NULL, /* apply negotiated features */
|
||||
virtio_hyper_dmabuf_set_status, /* called on guest set status */
|
||||
0, /* our capabilities */
|
||||
};
|
||||
|
||||
static int
|
||||
virtio_hyper_dmabuf_k_init()
|
||||
{
|
||||
if (vbs_k_hyper_dmabuf_fd != -1) {
|
||||
WPRINTF("virtio_hyper_dmabuf: Ooops! Re-entered!!\n");
|
||||
return -VIRTIO_ERROR_REENTER;
|
||||
}
|
||||
|
||||
vbs_k_hyper_dmabuf_fd = open(hyper_dmabuf_vbs_dev_path, O_RDWR);
|
||||
if (vbs_k_hyper_dmabuf_fd < 0) {
|
||||
WPRINTF("virtio_hyper_dmabuf: Failed to open %s!\n",
|
||||
hyper_dmabuf_vbs_dev_path);
|
||||
return -VIRTIO_ERROR_FD_OPEN_FAILED;
|
||||
}
|
||||
DPRINTF("virtio_hyper_dmabuf: Open %s success!\n",
|
||||
hyper_dmabuf_vbs_dev_path);
|
||||
|
||||
memset(&kdev, 0, sizeof(kdev));
|
||||
memset(&kvqs, 0, sizeof(kvqs));
|
||||
|
||||
return VIRTIO_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_hyper_dmabuf_k_dev_set(const char *name, int vmid, int nvq,
|
||||
uint32_t feature, uint64_t pio_start,
|
||||
uint64_t pio_len)
|
||||
{
|
||||
/* init kdev */
|
||||
strncpy(kdev.name, name, VBS_NAME_LEN);
|
||||
kdev.vmid = vmid;
|
||||
kdev.nvq = nvq;
|
||||
kdev.negotiated_features = feature;
|
||||
kdev.pio_range_start = pio_start;
|
||||
kdev.pio_range_len = pio_len;
|
||||
|
||||
return VIRTIO_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_hyper_dmabuf_k_vq_set(unsigned int nvq, unsigned int idx,
|
||||
uint16_t qsize, uint32_t pfn,
|
||||
uint16_t msix_idx, uint64_t msix_addr,
|
||||
uint32_t msix_data)
|
||||
{
|
||||
if (nvq <= idx) {
|
||||
WPRINTF("virtio_hyper_dmabuf: wrong idx for vq_set!\n");
|
||||
return -VIRTIO_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
/* init kvqs */
|
||||
kvqs.nvq = nvq;
|
||||
kvqs.vqs[idx].qsize = qsize;
|
||||
kvqs.vqs[idx].pfn = pfn;
|
||||
kvqs.vqs[idx].msix_idx = msix_idx;
|
||||
kvqs.vqs[idx].msix_addr = msix_addr;
|
||||
kvqs.vqs[idx].msix_data = msix_data;
|
||||
|
||||
return VIRTIO_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_hyper_dmabuf_k_start(void)
|
||||
{
|
||||
if (vbs_kernel_start(vbs_k_hyper_dmabuf_fd, &kdev, &kvqs) < 0) {
|
||||
WPRINTF("virtio_hyper_dmabuf: Failed in vbs_kernel_start!\n");
|
||||
return -VIRTIO_ERROR_START;
|
||||
}
|
||||
|
||||
DPRINTF("virtio_hyper_dmabuf: vbs_kernel_started!\n");
|
||||
return VIRTIO_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_hyper_dmabuf_k_stop(void)
|
||||
{
|
||||
return vbs_kernel_stop(vbs_k_hyper_dmabuf_fd);
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_hyper_dmabuf_k_reset(void)
|
||||
{
|
||||
memset(&kdev, 0, sizeof(kdev));
|
||||
memset(&kvqs, 0, sizeof(kvqs));
|
||||
|
||||
return vbs_kernel_reset(vbs_k_hyper_dmabuf_fd);
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_hyper_dmabuf_reset(void *base)
|
||||
{
|
||||
struct virtio_hyper_dmabuf *hyper_dmabuf;
|
||||
|
||||
hyper_dmabuf = (struct virtio_hyper_dmabuf *)base;
|
||||
|
||||
DPRINTF("virtio_hyper_dmabuf: device reset requested !\n");
|
||||
virtio_reset_dev(&hyper_dmabuf->base);
|
||||
if (kstatus == VIRTIO_DEV_STARTED) {
|
||||
virtio_hyper_dmabuf_k_stop();
|
||||
virtio_hyper_dmabuf_k_reset();
|
||||
kstatus = VIRTIO_DEV_INITIAL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_hyper_dmabuf_no_notify(void *base, struct virtio_vq_info *vq)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* This callback gives us a chance to determine the timings
|
||||
* to kickoff VBS-K initialization
|
||||
*/
|
||||
static void
|
||||
virtio_hyper_dmabuf_set_status(void *base, uint64_t status)
|
||||
{
|
||||
struct virtio_hyper_dmabuf *hyper_dmabuf;
|
||||
int nvq;
|
||||
struct msix_table_entry *mte;
|
||||
uint64_t msix_addr = 0;
|
||||
uint32_t msix_data = 0;
|
||||
int rc, i, j;
|
||||
|
||||
hyper_dmabuf = (struct virtio_hyper_dmabuf *) base;
|
||||
nvq = hyper_dmabuf->base.vops->nvq;
|
||||
|
||||
if (kstatus == VIRTIO_DEV_INIT_SUCCESS &&
|
||||
(status & VIRTIO_CR_STATUS_DRIVER_OK)) {
|
||||
/* time to kickoff VBS-K side */
|
||||
/* init vdev first */
|
||||
rc = virtio_hyper_dmabuf_k_dev_set(
|
||||
hyper_dmabuf->base.vops->name,
|
||||
hyper_dmabuf->base.dev->vmctx->vmid,
|
||||
nvq,
|
||||
hyper_dmabuf->base.negotiated_caps,
|
||||
/* currently we let VBS-K handle
|
||||
* kick register
|
||||
*/
|
||||
hyper_dmabuf->base.dev->bar[0].addr + 16,
|
||||
2);
|
||||
|
||||
for (i = 0; i < nvq; i++) {
|
||||
if (hyper_dmabuf->vq[i].msix_idx !=
|
||||
VIRTIO_MSI_NO_VECTOR) {
|
||||
j = hyper_dmabuf->vq[i].msix_idx;
|
||||
mte = &hyper_dmabuf->base.dev->msix.table[j];
|
||||
msix_addr = mte->addr;
|
||||
msix_data = mte->msg_data;
|
||||
}
|
||||
rc = virtio_hyper_dmabuf_k_vq_set(
|
||||
nvq, i,
|
||||
hyper_dmabuf->vq[i].qsize,
|
||||
hyper_dmabuf->vq[i].pfn,
|
||||
hyper_dmabuf->vq[i].msix_idx,
|
||||
msix_addr,
|
||||
msix_data);
|
||||
|
||||
if (rc < 0) {
|
||||
WPRINTF("virtio_hyper_dmabuf:");
|
||||
WPRINTF("kernel_set_vq");
|
||||
WPRINTF("failed, i %d ret %d\n", i, rc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
rc = virtio_hyper_dmabuf_k_start();
|
||||
if (rc < 0) {
|
||||
WPRINTF("virtio_hyper_dmabuf:");
|
||||
WPRINTF("kernel_start() failed\n");
|
||||
kstatus = VIRTIO_DEV_START_FAILED;
|
||||
} else {
|
||||
kstatus = VIRTIO_DEV_STARTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_hyper_dmabuf_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
||||
{
|
||||
struct virtio_hyper_dmabuf *hyper_dmabuf;
|
||||
|
||||
kstatus = VIRTIO_DEV_PRE_INIT;
|
||||
pthread_mutexattr_t attr;
|
||||
int rc;
|
||||
|
||||
hyper_dmabuf = calloc(1, sizeof(struct virtio_hyper_dmabuf));
|
||||
if (!hyper_dmabuf) {
|
||||
WPRINTF(("virtio_hdma: calloc returns NULL\n"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* init mutex attribute properly */
|
||||
rc = pthread_mutexattr_init(&attr);
|
||||
if (rc)
|
||||
DPRINTF("mutexattr init failed with erro %d!\n", rc);
|
||||
|
||||
if (fbsdrun_virtio_msix()) {
|
||||
rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
|
||||
DPRINTF("virtio_msix: mutexattr_settype ");
|
||||
DPRINTF("failed with error %d!\n", rc);
|
||||
} else {
|
||||
rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
DPRINTF("virtio_intx: mutexattr_settype ");
|
||||
DPRINTF("failed with error %d!\n", rc);
|
||||
}
|
||||
|
||||
rc = pthread_mutex_init(&hyper_dmabuf->mtx, &attr);
|
||||
if (rc)
|
||||
DPRINTF("mutex init failed with error %d!\n", rc);
|
||||
|
||||
virtio_linkup(&hyper_dmabuf->base,
|
||||
&virtio_hyper_dmabuf_ops_k,
|
||||
hyper_dmabuf,
|
||||
dev,
|
||||
hyper_dmabuf->vq);
|
||||
|
||||
rc = virtio_hyper_dmabuf_k_init();
|
||||
if (rc < 0) {
|
||||
WPRINTF("virtio_hyper_dmabuf: VBS-K ");
|
||||
WPRINTF("init failed with error %d!\n", rc);
|
||||
kstatus = VIRTIO_DEV_INIT_FAILED;
|
||||
} else {
|
||||
kstatus = VIRTIO_DEV_INIT_SUCCESS;
|
||||
}
|
||||
|
||||
hyper_dmabuf->base.mtx = &hyper_dmabuf->mtx;
|
||||
|
||||
hyper_dmabuf->vq[0].qsize = HYPER_DMABUF_RINGSZ;
|
||||
hyper_dmabuf->vq[1].qsize = HYPER_DMABUF_RINGSZ;
|
||||
|
||||
/* initialize config space */
|
||||
pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_HYPERDMABUF);
|
||||
pci_set_cfgdata16(dev, PCIR_VENDOR, INTEL_VENDOR_ID);
|
||||
pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_MEMORY);
|
||||
pci_set_cfgdata16(dev, PCIR_SUBDEV_0, VIRTIO_TYPE_HYPERDMABUF);
|
||||
pci_set_cfgdata16(dev, PCIR_SUBVEND_0, INTEL_VENDOR_ID);
|
||||
|
||||
if (virtio_interrupt_init(&hyper_dmabuf->base, fbsdrun_virtio_msix())) {
|
||||
if (hyper_dmabuf)
|
||||
free(hyper_dmabuf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
virtio_set_io_bar(&hyper_dmabuf->base, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_hyper_dmabuf_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
||||
{
|
||||
if (kstatus == VIRTIO_DEV_STARTED) {
|
||||
DPRINTF("virtio_hyper_dmabuf: deinitializing\n");
|
||||
virtio_hyper_dmabuf_k_stop();
|
||||
virtio_hyper_dmabuf_k_reset();
|
||||
kstatus = VIRTIO_DEV_INITIAL;
|
||||
assert(vbs_k_hyper_dmabuf_fd >= 0);
|
||||
close(vbs_k_hyper_dmabuf_fd);
|
||||
vbs_k_hyper_dmabuf_fd = -1;
|
||||
}
|
||||
|
||||
if (dev->arg)
|
||||
free((struct virtio_hyper_dmabuf *)dev->arg);
|
||||
}
|
||||
|
||||
struct pci_vdev_ops pci_ops_virtio_hyper_dmabuf = {
|
||||
.class_name = "virtio-hyper_dmabuf",
|
||||
.vdev_init = virtio_hyper_dmabuf_init,
|
||||
.vdev_deinit = virtio_hyper_dmabuf_deinit,
|
||||
.vdev_barwrite = virtio_pci_write,
|
||||
.vdev_barread = virtio_pci_read
|
||||
};
|
||||
DEFINE_PCI_DEVTYPE(pci_ops_virtio_hyper_dmabuf);
|
||||
104
devicemodel/hw/pci/virtio/virtio_kernel.c
Normal file
104
devicemodel/hw/pci/virtio/virtio_kernel.c
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Intel Corporation. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * 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.
|
||||
* * Neither the name of Intel Corporation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
|
||||
* OWNER 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/* Routines to notify the VBS-K in kernel */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "virtio_kernel.h"
|
||||
|
||||
static int virtio_kernel_debug;
|
||||
#define DPRINTF(params) do { if (virtio_kernel_debug) printf params; } while (0)
|
||||
#define WPRINTF(params) (printf params)
|
||||
|
||||
static int
|
||||
vbs_dev_info_set(int fd, void *arg)
|
||||
{
|
||||
return ioctl(fd, VBS_K_SET_DEV, arg);
|
||||
}
|
||||
|
||||
static int
|
||||
vbs_vqs_info_set(int fd, void *arg)
|
||||
{
|
||||
return ioctl(fd, VBS_K_SET_VQ, arg);
|
||||
}
|
||||
|
||||
/* VBS-K common ops */
|
||||
/* VBS-K init/reset */
|
||||
int
|
||||
vbs_kernel_init(int fd)
|
||||
{
|
||||
return VIRTIO_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
vbs_kernel_reset(int fd)
|
||||
{
|
||||
return VIRTIO_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* We need a way to start/stop vbs_k execution since guest might want to
|
||||
* change the configuration of the virtio device after VBS-K has been
|
||||
* initialized.
|
||||
*/
|
||||
/* VBS-K start/stop */
|
||||
int
|
||||
vbs_kernel_start(int fd, struct vbs_dev_info *dev, struct vbs_vqs_info *vqs)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (fd < 0) {
|
||||
WPRINTF(("%s: fd < 0\n", __func__));
|
||||
return -VIRTIO_ERROR_FD_OPEN_FAILED;
|
||||
}
|
||||
|
||||
ret = vbs_dev_info_set(fd, dev);
|
||||
if (ret < 0) {
|
||||
WPRINTF(("vbs_kernel_set_dev failed: ret %d\n", ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = vbs_vqs_info_set(fd, vqs);
|
||||
if (ret < 0) {
|
||||
WPRINTF(("vbs_kernel_set_vqs failed: ret %d\n", ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return VIRTIO_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
vbs_kernel_stop(int fd)
|
||||
{
|
||||
DPRINTF(("%s\n", __func__));
|
||||
return VIRTIO_SUCCESS;
|
||||
}
|
||||
1103
devicemodel/hw/pci/virtio/virtio_net.c
Normal file
1103
devicemodel/hw/pci/virtio/virtio_net.c
Normal file
File diff suppressed because it is too large
Load Diff
482
devicemodel/hw/pci/virtio/virtio_rnd.c
Normal file
482
devicemodel/hw/pci/virtio/virtio_rnd.c
Normal file
@@ -0,0 +1,482 @@
|
||||
/*-
|
||||
* Copyright (c) 2014 Nahanni Systems Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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
|
||||
* in this position and unchanged.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* virtio entropy device emulation.
|
||||
* Randomness is sourced from /dev/random which does not block
|
||||
* once it has been seeded at bootup.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/uio.h>
|
||||
#include <err.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <sysexits.h>
|
||||
|
||||
#include "dm.h"
|
||||
#include "pci_core.h"
|
||||
#include "virtio.h"
|
||||
#include "virtio_kernel.h"
|
||||
#include "vmmapi.h" /* for vmctx */
|
||||
|
||||
#define VIRTIO_RND_RINGSZ 64
|
||||
|
||||
/*
|
||||
* Per-device struct
|
||||
*/
|
||||
struct virtio_rnd {
|
||||
/* VBS-U variables */
|
||||
struct virtio_base base;
|
||||
struct virtio_vq_info vq;
|
||||
pthread_mutex_t mtx;
|
||||
uint64_t cfg;
|
||||
int fd;
|
||||
/* VBS-K variables */
|
||||
struct {
|
||||
enum VBS_K_STATUS status;
|
||||
int fd;
|
||||
struct vbs_dev_info dev;
|
||||
struct vbs_vqs_info vqs;
|
||||
} vbs_k;
|
||||
};
|
||||
|
||||
static int virtio_rnd_debug;
|
||||
#define DPRINTF(params) do { if (virtio_rnd_debug) printf params; } while (0)
|
||||
#define WPRINTF(params) (printf params)
|
||||
|
||||
/* VBS-K interface functions */
|
||||
static int virtio_rnd_kernel_init(struct virtio_rnd *); /* open VBS-K chardev */
|
||||
static int virtio_rnd_kernel_start(struct virtio_rnd *);
|
||||
static int virtio_rnd_kernel_stop(struct virtio_rnd *);
|
||||
static int virtio_rnd_kernel_reset(struct virtio_rnd *);
|
||||
static int virtio_rnd_kernel_dev_set(struct vbs_dev_info *kdev,
|
||||
const char *name, int vmid, int nvq,
|
||||
uint32_t feature, uint64_t pio_start,
|
||||
uint64_t pio_len);
|
||||
static int virtio_rnd_kernel_vq_set(struct vbs_vqs_info *kvqs, unsigned int nvq,
|
||||
unsigned int idx, uint16_t qsize,
|
||||
uint32_t pfn, uint16_t msix_idx,
|
||||
uint64_t msix_addr, uint32_t msix_data);
|
||||
|
||||
/* VBS-U virtio_ops */
|
||||
static void virtio_rnd_reset(void *);
|
||||
static void virtio_rnd_notify(void *, struct virtio_vq_info *);
|
||||
static struct virtio_ops virtio_rnd_ops = {
|
||||
"virtio_rnd", /* our name */
|
||||
1, /* we support 1 virtqueue */
|
||||
0, /* config reg size */
|
||||
virtio_rnd_reset, /* reset */
|
||||
virtio_rnd_notify, /* device-wide qnotify */
|
||||
NULL, /* read virtio config */
|
||||
NULL, /* write virtio config */
|
||||
NULL, /* apply negotiated features */
|
||||
NULL, /* called on guest set status */
|
||||
0, /* our capabilities */
|
||||
};
|
||||
|
||||
/* VBS-K virtio_ops */
|
||||
static void virtio_rnd_k_no_notify(void *, struct virtio_vq_info *);
|
||||
static void virtio_rnd_k_set_status(void *, uint64_t);
|
||||
static struct virtio_ops virtio_rnd_ops_k = {
|
||||
"virtio_rnd", /* our name */
|
||||
1, /* we support 1 virtqueue */
|
||||
0, /* config reg size */
|
||||
virtio_rnd_reset, /* reset */
|
||||
virtio_rnd_k_no_notify, /* device-wide qnotify */
|
||||
NULL, /* read virtio config */
|
||||
NULL, /* write virtio config */
|
||||
NULL, /* apply negotiated features */
|
||||
virtio_rnd_k_set_status,/* called on guest set status */
|
||||
0, /* our capabilities */
|
||||
};
|
||||
|
||||
/* VBS-K interface function implementations */
|
||||
static void
|
||||
virtio_rnd_k_no_notify(void *base, struct virtio_vq_info *vq)
|
||||
{
|
||||
WPRINTF(("virtio_rnd: VBS-K mode! Should not reach here!!\n"));
|
||||
}
|
||||
|
||||
/*
|
||||
* This callback gives us a chance to determine the timings
|
||||
* to kickoff VBS-K initialization
|
||||
*/
|
||||
static void
|
||||
virtio_rnd_k_set_status(void *base, uint64_t status)
|
||||
{
|
||||
struct virtio_rnd *rnd;
|
||||
int nvq;
|
||||
struct msix_table_entry *mte;
|
||||
uint64_t msix_addr = 0;
|
||||
uint32_t msix_data = 0;
|
||||
int rc, i, j;
|
||||
|
||||
rnd = base;
|
||||
nvq = rnd->base.vops->nvq;
|
||||
|
||||
if (rnd->vbs_k.status == VIRTIO_DEV_INIT_SUCCESS &&
|
||||
(status & VIRTIO_CR_STATUS_DRIVER_OK)) {
|
||||
/* time to kickoff VBS-K side */
|
||||
/* init vdev first */
|
||||
rc = virtio_rnd_kernel_dev_set(&rnd->vbs_k.dev,
|
||||
rnd->base.vops->name,
|
||||
rnd->base.dev->vmctx->vmid,
|
||||
nvq,
|
||||
rnd->base.negotiated_caps,
|
||||
/*
|
||||
* currently we let VBS-K handle
|
||||
* kick register
|
||||
*/
|
||||
rnd->base.dev->bar[0].addr + 16,
|
||||
2);
|
||||
|
||||
for (i = 0; i < nvq; i++) {
|
||||
if (rnd->vq.msix_idx != VIRTIO_MSI_NO_VECTOR) {
|
||||
j = rnd->vq.msix_idx;
|
||||
mte = &rnd->base.dev->msix.table[j];
|
||||
msix_addr = mte->addr;
|
||||
msix_data = mte->msg_data;
|
||||
}
|
||||
rc = virtio_rnd_kernel_vq_set(&rnd->vbs_k.vqs,
|
||||
nvq, i,
|
||||
rnd->vq.qsize,
|
||||
rnd->vq.pfn,
|
||||
rnd->vq.msix_idx,
|
||||
msix_addr,
|
||||
msix_data);
|
||||
if (rc < 0) {
|
||||
WPRINTF(("rnd_kernel_set_vq fail,i %d ret %d\n",
|
||||
i, rc));
|
||||
return;
|
||||
}
|
||||
}
|
||||
rc = virtio_rnd_kernel_start(rnd);
|
||||
if (rc < 0) {
|
||||
WPRINTF(("virtio_rnd_kernel_start() failed\n"));
|
||||
rnd->vbs_k.status = VIRTIO_DEV_START_FAILED;
|
||||
} else {
|
||||
rnd->vbs_k.status = VIRTIO_DEV_STARTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Called in virtio_rnd_init(), where the initialization of the
|
||||
* PCIe device emulation is still on the way by device model.
|
||||
*/
|
||||
static int
|
||||
virtio_rnd_kernel_init(struct virtio_rnd *rnd)
|
||||
{
|
||||
assert(rnd->vbs_k.fd == 0);
|
||||
|
||||
rnd->vbs_k.fd = open("/dev/vbs_rng", O_RDWR);
|
||||
if (rnd->vbs_k.fd < 0) {
|
||||
WPRINTF(("Failed to open /dev/vbs_k_rng!\n"));
|
||||
return -VIRTIO_ERROR_FD_OPEN_FAILED;
|
||||
}
|
||||
DPRINTF(("Open /dev/vbs_rng success!\n"));
|
||||
|
||||
memset(&rnd->vbs_k.dev, 0, sizeof(struct vbs_dev_info));
|
||||
memset(&rnd->vbs_k.vqs, 0, sizeof(struct vbs_vqs_info));
|
||||
|
||||
return VIRTIO_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_rnd_kernel_dev_set(struct vbs_dev_info *kdev, const char *name,
|
||||
int vmid, int nvq, uint32_t feature,
|
||||
uint64_t pio_start, uint64_t pio_len)
|
||||
{
|
||||
/* FE driver has set VIRTIO_CONFIG_S_DRIVER_OK */
|
||||
|
||||
/* init kdev */
|
||||
strncpy(kdev->name, name, VBS_NAME_LEN);
|
||||
kdev->vmid = vmid;
|
||||
kdev->nvq = nvq;
|
||||
kdev->negotiated_features = feature;
|
||||
kdev->pio_range_start = pio_start;
|
||||
kdev->pio_range_len = pio_len;
|
||||
|
||||
return VIRTIO_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_rnd_kernel_vq_set(struct vbs_vqs_info *kvqs, unsigned int nvq,
|
||||
unsigned int idx, uint16_t qsize, uint32_t pfn,
|
||||
uint16_t msix_idx, uint64_t msix_addr,
|
||||
uint32_t msix_data)
|
||||
{
|
||||
/* FE driver has set VIRTIO_CONFIG_S_DRIVER_OK */
|
||||
if (nvq <= idx) {
|
||||
WPRINTF(("%s: wrong idx!\n", __func__));
|
||||
return -VIRTIO_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
/* init kvqs */
|
||||
kvqs->nvq = nvq;
|
||||
kvqs->vqs[idx].qsize = qsize;
|
||||
kvqs->vqs[idx].pfn = pfn;
|
||||
kvqs->vqs[idx].msix_idx = msix_idx;
|
||||
kvqs->vqs[idx].msix_addr = msix_addr;
|
||||
kvqs->vqs[idx].msix_data = msix_data;
|
||||
|
||||
return VIRTIO_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_rnd_kernel_start(struct virtio_rnd *rnd)
|
||||
{
|
||||
if (vbs_kernel_start(rnd->vbs_k.fd,
|
||||
&rnd->vbs_k.dev,
|
||||
&rnd->vbs_k.vqs) < 0) {
|
||||
WPRINTF(("Failed in vbs_k_start!\n"));
|
||||
return -VIRTIO_ERROR_START;
|
||||
}
|
||||
|
||||
DPRINTF(("vbs_k_started!\n"));
|
||||
return VIRTIO_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_rnd_kernel_stop(struct virtio_rnd *rnd)
|
||||
{
|
||||
/* device specific cleanups here */
|
||||
return vbs_kernel_stop(rnd->vbs_k.fd);
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_rnd_kernel_reset(struct virtio_rnd *rnd)
|
||||
{
|
||||
memset(&rnd->vbs_k.dev, 0, sizeof(struct vbs_dev_info));
|
||||
memset(&rnd->vbs_k.vqs, 0, sizeof(struct vbs_vqs_info));
|
||||
|
||||
return vbs_kernel_reset(rnd->vbs_k.fd);
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_rnd_reset(void *base)
|
||||
{
|
||||
struct virtio_rnd *rnd;
|
||||
|
||||
rnd = base;
|
||||
|
||||
DPRINTF(("virtio_rnd: device reset requested !\n"));
|
||||
virtio_reset_dev(&rnd->base);
|
||||
DPRINTF(("virtio_rnd: kstatus %d\n", rnd->vbs_k.status));
|
||||
if (rnd->vbs_k.status == VIRTIO_DEV_STARTED) {
|
||||
DPRINTF(("virtio_rnd: VBS-K reset requested!\n"));
|
||||
virtio_rnd_kernel_stop(rnd);
|
||||
virtio_rnd_kernel_reset(rnd);
|
||||
rnd->vbs_k.status = VIRTIO_DEV_INITIAL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_rnd_notify(void *base, struct virtio_vq_info *vq)
|
||||
{
|
||||
struct iovec iov;
|
||||
struct virtio_rnd *rnd;
|
||||
int len;
|
||||
uint16_t idx;
|
||||
|
||||
rnd = base;
|
||||
|
||||
if (rnd->fd < 0) {
|
||||
vq_endchains(vq, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
while (vq_has_descs(vq)) {
|
||||
vq_getchain(vq, &idx, &iov, 1, NULL);
|
||||
|
||||
len = read(rnd->fd, iov.iov_base, iov.iov_len);
|
||||
|
||||
DPRINTF(("%s: %d\r\n", __func__, len));
|
||||
|
||||
/* Catastrophe if unable to read from /dev/random */
|
||||
assert(len > 0);
|
||||
|
||||
/*
|
||||
* Release this chain and handle more
|
||||
*/
|
||||
vq_relchain(vq, idx, len);
|
||||
}
|
||||
vq_endchains(vq, 1); /* Generate interrupt if appropriate. */
|
||||
}
|
||||
|
||||
static int
|
||||
virtio_rnd_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
||||
{
|
||||
struct virtio_rnd *rnd;
|
||||
int fd;
|
||||
int len;
|
||||
uint8_t v;
|
||||
pthread_mutexattr_t attr;
|
||||
int rc;
|
||||
char *opt;
|
||||
char *vbs_k_opt = NULL;
|
||||
enum VBS_K_STATUS kstat = VIRTIO_DEV_INITIAL;
|
||||
|
||||
while ((opt = strsep(&opts, ",")) != NULL) {
|
||||
/* vbs_k_opt should be kernel=on */
|
||||
vbs_k_opt = strsep(&opt, "=");
|
||||
DPRINTF(("vbs_k_opt is %s\n", vbs_k_opt));
|
||||
if (opt != NULL) {
|
||||
if (strncmp(opt, "on", 2) == 0)
|
||||
kstat = VIRTIO_DEV_PRE_INIT;
|
||||
WPRINTF(("virtio_rnd: VBS-K initializing..."));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Should always be able to open /dev/random.
|
||||
*/
|
||||
fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
/*
|
||||
* Check that device is seeded and non-blocking.
|
||||
*/
|
||||
len = read(fd, &v, sizeof(v));
|
||||
if (len <= 0) {
|
||||
WPRINTF(("virtio_rnd: /dev/random not ready, read(): %d", len));
|
||||
return -1;
|
||||
}
|
||||
|
||||
rnd = calloc(1, sizeof(struct virtio_rnd));
|
||||
if (!rnd) {
|
||||
WPRINTF(("virtio_rnd: calloc returns NULL\n"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
rnd->vbs_k.status = kstat;
|
||||
|
||||
/* init mutex attribute properly */
|
||||
rc = pthread_mutexattr_init(&attr);
|
||||
if (rc)
|
||||
DPRINTF(("mutexattr init failed with erro %d!\n", rc));
|
||||
if (fbsdrun_virtio_msix()) {
|
||||
rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
|
||||
if (rc)
|
||||
DPRINTF(("virtio_msix: mutexattr_settype failed with "
|
||||
"error %d!\n", rc));
|
||||
} else {
|
||||
rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
if (rc)
|
||||
DPRINTF(("virtio_intx: mutexattr_settype failed with "
|
||||
"error %d!\n", rc));
|
||||
}
|
||||
rc = pthread_mutex_init(&rnd->mtx, &attr);
|
||||
if (rc)
|
||||
DPRINTF(("mutex init failed with error %d!\n", rc));
|
||||
|
||||
if (rnd->vbs_k.status == VIRTIO_DEV_PRE_INIT) {
|
||||
DPRINTF(("%s: VBS-K option detected!\n", __func__));
|
||||
virtio_linkup(&rnd->base, &virtio_rnd_ops_k,
|
||||
rnd, dev, &rnd->vq);
|
||||
rc = virtio_rnd_kernel_init(rnd);
|
||||
if (rc < 0) {
|
||||
WPRINTF(("virtio_rnd: VBS-K init failed,error %d!\n",
|
||||
rc));
|
||||
rnd->vbs_k.status = VIRTIO_DEV_INIT_FAILED;
|
||||
} else {
|
||||
rnd->vbs_k.status = VIRTIO_DEV_INIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
if (rnd->vbs_k.status == VIRTIO_DEV_INITIAL ||
|
||||
rnd->vbs_k.status != VIRTIO_DEV_INIT_SUCCESS) {
|
||||
DPRINTF(("%s: fallback to VBS-U...\n", __func__));
|
||||
virtio_linkup(&rnd->base, &virtio_rnd_ops, rnd, dev, &rnd->vq);
|
||||
}
|
||||
|
||||
rnd->base.mtx = &rnd->mtx;
|
||||
|
||||
rnd->vq.qsize = VIRTIO_RND_RINGSZ;
|
||||
|
||||
/* keep /dev/random opened while emulating */
|
||||
rnd->fd = fd;
|
||||
|
||||
/* initialize config space */
|
||||
pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_RANDOM);
|
||||
pci_set_cfgdata16(dev, PCIR_VENDOR, VIRTIO_VENDOR);
|
||||
pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_CRYPTO);
|
||||
pci_set_cfgdata16(dev, PCIR_SUBDEV_0, VIRTIO_TYPE_ENTROPY);
|
||||
pci_set_cfgdata16(dev, PCIR_SUBVEND_0, VIRTIO_VENDOR);
|
||||
|
||||
if (virtio_interrupt_init(&rnd->base, fbsdrun_virtio_msix())) {
|
||||
if (rnd)
|
||||
free(rnd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
virtio_set_io_bar(&rnd->base, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
virtio_rnd_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
||||
{
|
||||
struct virtio_rnd *rnd;
|
||||
|
||||
rnd = dev->arg;
|
||||
if (rnd == NULL) {
|
||||
DPRINTF(("%s: rnd is NULL\n", __func__));
|
||||
return;
|
||||
}
|
||||
|
||||
if (rnd->vbs_k.status == VIRTIO_DEV_STARTED) {
|
||||
DPRINTF(("%s: deinit virtio_rnd_k!\n", __func__));
|
||||
virtio_rnd_kernel_stop(rnd);
|
||||
virtio_rnd_kernel_reset(rnd);
|
||||
rnd->vbs_k.status = VIRTIO_DEV_INITIAL;
|
||||
assert(rnd->vbs_k.fd >= 0);
|
||||
close(rnd->vbs_k.fd);
|
||||
rnd->vbs_k.fd = -1;
|
||||
}
|
||||
|
||||
DPRINTF(("%s: free struct virtio_rnd!\n", __func__));
|
||||
free(rnd);
|
||||
}
|
||||
|
||||
|
||||
struct pci_vdev_ops pci_ops_virtio_rnd = {
|
||||
.class_name = "virtio-rnd",
|
||||
.vdev_init = virtio_rnd_init,
|
||||
.vdev_deinit = virtio_rnd_deinit,
|
||||
.vdev_barwrite = virtio_pci_write,
|
||||
.vdev_barread = virtio_pci_read
|
||||
};
|
||||
DEFINE_PCI_DEVTYPE(pci_ops_virtio_rnd);
|
||||
361
devicemodel/hw/pci/wdt_i6300esb.c
Normal file
361
devicemodel/hw/pci/wdt_i6300esb.c
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Intel Corporation. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * 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.
|
||||
* * Neither the name of Intel Corporation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
|
||||
* OWNER 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* WatchDog Timer (WDT): emulate i6300esb PCI wdt Intel SOC devices,
|
||||
* used to monitor guest OS
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "vmm.h"
|
||||
#include "vmmapi.h"
|
||||
#include "mevent.h"
|
||||
#include "pci_core.h"
|
||||
|
||||
#define WDT_REG_BAR_SIZE 0x10
|
||||
|
||||
#define PCI_VENDOR_ID_INTEL 0x8086
|
||||
#define PCI_DEVICE_ID_INTEL_ESB 0x25ab
|
||||
|
||||
#define ESB_CONFIG_REG 0x60 /* Config register*/
|
||||
#define ESB_LOCK_REG 0x68 /* WDT lock register*/
|
||||
|
||||
/* Memory mapped registers */
|
||||
#define ESB_TIMER1_REG 0x00 /* Timer1 value after each reset */
|
||||
#define ESB_TIMER2_REG 0x04 /* Timer2 value after each reset */
|
||||
#define ESB_RELOAD_REG 0x0c /* Reload register */
|
||||
|
||||
#define ESB_WDT_ENABLE (0x01 << 1) /* Enable WDT */
|
||||
#define ESB_WDT_LOCK (0x01 << 0) /* Lock (nowayout) */
|
||||
|
||||
#define ESB_WDT_REBOOT (0x01 << 5) /* Enable reboot on timeout */
|
||||
#define ESB_WDT_RELOAD (0x01 << 8) /* Ping/kick dog */
|
||||
#define TIMER_TO_SECONDS(val) (val >> 9)
|
||||
|
||||
/* Magic constants */
|
||||
#define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */
|
||||
#define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */
|
||||
|
||||
#define WDT_TIMER_SIG 0x55AA
|
||||
#define DEFAULT_MAX_TIMER_VAL 0x000FFFFF
|
||||
|
||||
/* for debug */
|
||||
/* #define WDT_DEBUG */
|
||||
#ifdef WDT_DEBUG
|
||||
static FILE * dbg_file;
|
||||
#define DPRINTF(format, args...) \
|
||||
do { fprintf(dbg_file, format, args); fflush(dbg_file); } while (0)
|
||||
#else
|
||||
#define DPRINTF(format, arg...)
|
||||
#endif
|
||||
|
||||
struct info_wdt {
|
||||
bool reboot_enabled;/* "reboot" on wdt out */
|
||||
|
||||
bool locked; /* If true, enabled field cannot be changed. */
|
||||
bool wdt_enabled; /* If true, watchdog is enabled. */
|
||||
|
||||
bool timer_created;
|
||||
timer_t wdt_timerid;
|
||||
|
||||
uint32_t timer1_val;
|
||||
uint32_t timer2_val;
|
||||
int stage; /* stage 1 or 2. */
|
||||
|
||||
int unlock_state; /* unlock states 0 -> 1 -> 2 */
|
||||
};
|
||||
|
||||
static struct info_wdt wdt_state;
|
||||
|
||||
static void start_wdt_timer(void);
|
||||
|
||||
/*
|
||||
* WDT timer, start when guest OS start watchdog service; and re-start for
|
||||
* each dog-kick / ping action if time out, it will trigger reboot or other
|
||||
* action to guest OS
|
||||
*/
|
||||
static void
|
||||
wdt_expired_thread(union sigval v)
|
||||
{
|
||||
DPRINTF("wdt timer out! id=0x%x, stage=%d, reboot=%d\n",
|
||||
v.sival_int, wdt_state.stage, wdt_state.reboot_enabled);
|
||||
|
||||
if (wdt_state.stage == 1) {
|
||||
wdt_state.stage = 2;
|
||||
start_wdt_timer();
|
||||
} else {
|
||||
if (wdt_state.reboot_enabled) {
|
||||
/* watchdog timer out, set the uos to reboot */
|
||||
vm_set_suspend_mode(VM_SUSPEND_RESET);
|
||||
mevent_notify();
|
||||
} else {
|
||||
/* if not need reboot, just loop timer */
|
||||
wdt_state.stage = 1;
|
||||
start_wdt_timer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
stop_wdt_timer()
|
||||
{
|
||||
struct itimerspec timer_val;
|
||||
|
||||
DPRINTF("%s: timer_created=%d\n", __func__, wdt_state.timer_created);
|
||||
|
||||
if (!wdt_state.timer_created)
|
||||
return;
|
||||
|
||||
memset(&timer_val, 0, sizeof(struct itimerspec));
|
||||
timer_settime(wdt_state.wdt_timerid, 0, &timer_val, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
reset_wdt_timer(int seconds)
|
||||
{
|
||||
struct itimerspec timer_val;
|
||||
|
||||
DPRINTF("%s: time=%d\n", __func__, seconds);
|
||||
memset(&timer_val, 0, sizeof(struct itimerspec));
|
||||
timer_settime(wdt_state.wdt_timerid, 0, &timer_val, NULL);
|
||||
|
||||
timer_val.it_value.tv_sec = seconds;
|
||||
if (timer_settime(wdt_state.wdt_timerid, 0, &timer_val, NULL) == -1) {
|
||||
perror("timer_settime failed.\n");
|
||||
timer_delete(wdt_state.wdt_timerid);
|
||||
wdt_state.timer_created = 0;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
start_wdt_timer(void)
|
||||
{
|
||||
int seconds;
|
||||
struct sigevent sig_evt;
|
||||
struct itimerspec timer_val;
|
||||
|
||||
if (!wdt_state.wdt_enabled)
|
||||
return;
|
||||
|
||||
if (wdt_state.stage == 1)
|
||||
seconds = TIMER_TO_SECONDS(wdt_state.timer1_val);
|
||||
else
|
||||
seconds = TIMER_TO_SECONDS(wdt_state.timer2_val);
|
||||
|
||||
DPRINTF("%s: created=%d, time=%d\n", __func__,
|
||||
wdt_state.timer_created, seconds);
|
||||
memset(&sig_evt, 0, sizeof(struct sigevent));
|
||||
if (wdt_state.timer_created) {
|
||||
reset_wdt_timer(seconds);
|
||||
return;
|
||||
}
|
||||
|
||||
sig_evt.sigev_value.sival_int = WDT_TIMER_SIG;
|
||||
sig_evt.sigev_notify = SIGEV_THREAD;
|
||||
sig_evt.sigev_notify_function = wdt_expired_thread;
|
||||
|
||||
if (timer_create(CLOCK_REALTIME, &sig_evt,
|
||||
&wdt_state.wdt_timerid) == -1) {
|
||||
perror("timer_create failed.\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
memset(&timer_val, 0, sizeof(struct itimerspec));
|
||||
timer_val.it_value.tv_sec = seconds;
|
||||
|
||||
if (timer_settime(wdt_state.wdt_timerid, 0, &timer_val, NULL) == -1) {
|
||||
perror("timer_settime failed.\n");
|
||||
timer_delete(wdt_state.wdt_timerid);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
wdt_state.timer_created = true;
|
||||
}
|
||||
|
||||
static int
|
||||
pci_wdt_cfg_read(struct vmctx *ctx, int vcpu, struct pci_vdev *dev,
|
||||
int offset, int bytes, uint32_t *rv)
|
||||
{
|
||||
int need_cfg = 1;
|
||||
|
||||
DPRINTF("%s: offset = %x, len = %d\n", __func__, offset, bytes);
|
||||
|
||||
if (offset == ESB_LOCK_REG && bytes == 1) {
|
||||
*rv = (wdt_state.locked ? ESB_WDT_LOCK : 0) |
|
||||
(wdt_state.wdt_enabled ? ESB_WDT_ENABLE : 0);
|
||||
|
||||
need_cfg = 0;
|
||||
}
|
||||
|
||||
return need_cfg;
|
||||
}
|
||||
|
||||
static int
|
||||
pci_wdt_cfg_write(struct vmctx *ctx, int vcpu, struct pci_vdev *dev,
|
||||
int offset, int bytes, uint32_t val)
|
||||
{
|
||||
bool old_flag;
|
||||
int need_cfg = 1;
|
||||
|
||||
DPRINTF("%s: offset = %x, len = %d, val = 0x%x\n",
|
||||
__func__, offset, bytes, val);
|
||||
|
||||
if (offset == ESB_CONFIG_REG && bytes == 2) {
|
||||
wdt_state.reboot_enabled = ((val & ESB_WDT_REBOOT) == 0);
|
||||
need_cfg = 0;
|
||||
|
||||
} else if (offset == ESB_LOCK_REG && bytes == 1) {
|
||||
if (!wdt_state.locked) {
|
||||
wdt_state.locked = ((val & ESB_WDT_LOCK) != 0);
|
||||
old_flag = wdt_state.wdt_enabled;
|
||||
wdt_state.wdt_enabled = ((val & ESB_WDT_ENABLE) != 0);
|
||||
|
||||
if (!old_flag && wdt_state.wdt_enabled) {
|
||||
wdt_state.stage = 1;
|
||||
start_wdt_timer();
|
||||
} else if (!wdt_state.wdt_enabled)
|
||||
stop_wdt_timer();
|
||||
}
|
||||
|
||||
need_cfg = 0;
|
||||
}
|
||||
|
||||
return need_cfg;
|
||||
}
|
||||
|
||||
static void
|
||||
pci_wdt_bar_write(struct vmctx *ctx, int vcpu, struct pci_vdev *dev,
|
||||
int baridx, uint64_t offset, int size, uint64_t value)
|
||||
{
|
||||
assert(baridx == 0);
|
||||
|
||||
DPRINTF("%s: addr = 0x%x, val = 0x%x, size=%d\n",
|
||||
__func__, (int) offset, (int)value, size);
|
||||
|
||||
if (offset == ESB_RELOAD_REG) {
|
||||
assert(size == 2);
|
||||
|
||||
if (value == ESB_UNLOCK1)
|
||||
wdt_state.unlock_state = 1;
|
||||
else if ((value == ESB_UNLOCK2)
|
||||
&& (wdt_state.unlock_state == 1))
|
||||
wdt_state.unlock_state = 2;
|
||||
else if ((wdt_state.unlock_state == 2)
|
||||
&& (value & ESB_WDT_RELOAD)) {
|
||||
wdt_state.stage = 1;
|
||||
start_wdt_timer();
|
||||
wdt_state.unlock_state = 0;
|
||||
}
|
||||
} else if (wdt_state.unlock_state == 2) {
|
||||
if (offset == ESB_TIMER1_REG)
|
||||
wdt_state.timer1_val = value & DEFAULT_MAX_TIMER_VAL;
|
||||
else if (offset == ESB_TIMER2_REG)
|
||||
wdt_state.timer2_val = value & DEFAULT_MAX_TIMER_VAL;
|
||||
|
||||
wdt_state.unlock_state = 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t
|
||||
pci_wdt_bar_read(struct vmctx *ctx, int vcpu, struct pci_vdev *dev,
|
||||
int baridx, uint64_t offset, int size)
|
||||
{
|
||||
assert(baridx == 0);
|
||||
DPRINTF("%s: addr = 0x%x, size=%d\n", __func__, (int) offset, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pci_wdt_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
||||
{
|
||||
/*the wdt just has one inistance */
|
||||
if (wdt_state.reboot_enabled && wdt_state.timer1_val) {
|
||||
perror("wdt can't be created twice, please check!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* init wdt state info */
|
||||
wdt_state.reboot_enabled = true;
|
||||
wdt_state.locked = false;
|
||||
wdt_state.timer_created = false;
|
||||
wdt_state.wdt_enabled = false;
|
||||
|
||||
wdt_state.stage = 1;
|
||||
wdt_state.timer1_val = DEFAULT_MAX_TIMER_VAL;
|
||||
wdt_state.timer2_val = DEFAULT_MAX_TIMER_VAL;
|
||||
wdt_state.unlock_state = 0;
|
||||
|
||||
pci_emul_alloc_bar(dev, 0, PCIBAR_MEM32, WDT_REG_BAR_SIZE);
|
||||
|
||||
/* initialize config space */
|
||||
pci_set_cfgdata16(dev, PCIR_VENDOR, PCI_VENDOR_ID_INTEL);
|
||||
pci_set_cfgdata16(dev, PCIR_DEVICE, PCI_DEVICE_ID_INTEL_ESB);
|
||||
pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_BASEPERIPH);
|
||||
pci_set_cfgdata8(dev, PCIR_SUBCLASS, PCIS_BASEPERIPH_OTHER);
|
||||
|
||||
#ifdef WDT_DEBUG
|
||||
dbg_file = fopen("/tmp/wdt_log", "w+");
|
||||
#endif
|
||||
|
||||
DPRINTF("%s: iobar =0x%lx, size=%ld\n", __func__,
|
||||
dev->bar[0].addr, dev->bar[0].size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
pci_wdt_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
||||
{
|
||||
stop_wdt_timer();
|
||||
memset(&wdt_state, 0, sizeof(wdt_state));
|
||||
}
|
||||
|
||||
struct pci_vdev_ops pci_ops_wdt = {
|
||||
.class_name = "wdt-i6300esb",
|
||||
.vdev_init = pci_wdt_init,
|
||||
.vdev_deinit = pci_wdt_deinit,
|
||||
.vdev_cfgwrite = pci_wdt_cfg_write,
|
||||
.vdev_cfgread = pci_wdt_cfg_read,
|
||||
.vdev_barwrite = pci_wdt_bar_write,
|
||||
.vdev_barread = pci_wdt_bar_read
|
||||
};
|
||||
|
||||
DEFINE_PCI_DEVTYPE(pci_ops_wdt);
|
||||
2917
devicemodel/hw/pci/xhci.c
Normal file
2917
devicemodel/hw/pci/xhci.c
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user