Changeset View
Changeset View
Standalone View
Standalone View
head/sys/dev/virtio/mmio/virtio_mmio.c
/*- | /*- | ||||
* Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com> | * Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com> | ||||
* Copyright (c) 2014 The FreeBSD Foundation | |||||
* All rights reserved. | * All rights reserved. | ||||
* | * | ||||
* This software was developed by SRI International and the University of | * This software was developed by SRI International and the University of | ||||
* Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) | * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) | ||||
* ("CTSRD"), as part of the DARPA CRASH research programme. | * ("CTSRD"), as part of the DARPA CRASH research programme. | ||||
* | * | ||||
* Portions of this software were developed by Andrew Turner | |||||
* under sponsorship from the FreeBSD Foundation. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | * Redistribution and use in source and binary forms, with or without | ||||
* modification, are permitted provided that the following conditions | * modification, are permitted provided that the following conditions | ||||
* are met: | * are met: | ||||
* 1. Redistributions of source code must retain the above copyright | * 1. Redistributions of source code must retain the above copyright | ||||
* notice, this list of conditions and the following disclaimer. | * notice, this list of conditions and the following disclaimer. | ||||
* 2. Redistributions in binary form must reproduce the above copyright | * 2. Redistributions in binary form must reproduce the above copyright | ||||
* notice, this list of conditions and the following disclaimer in the | * notice, this list of conditions and the following disclaimer in the | ||||
* documentation and/or other materials provided with the distribution. | * documentation and/or other materials provided with the distribution. | ||||
▲ Show 20 Lines • Show All 109 Lines • ▼ Show 20 Lines | |||||
static void vtmmio_release_child_resources(struct vtmmio_softc *); | static void vtmmio_release_child_resources(struct vtmmio_softc *); | ||||
static void vtmmio_reset(struct vtmmio_softc *); | static void vtmmio_reset(struct vtmmio_softc *); | ||||
static void vtmmio_select_virtqueue(struct vtmmio_softc *, int); | static void vtmmio_select_virtqueue(struct vtmmio_softc *, int); | ||||
static void vtmmio_vq_intr(void *); | static void vtmmio_vq_intr(void *); | ||||
/* | /* | ||||
* I/O port read/write wrappers. | * I/O port read/write wrappers. | ||||
*/ | */ | ||||
#define vtmmio_write_config_1(sc, o, v) \ | #define vtmmio_write_config_1(sc, o, v) \ | ||||
do { \ | |||||
bus_write_1((sc)->res[0], (o), (v)); \ | bus_write_1((sc)->res[0], (o), (v)); \ | ||||
VIRTIO_MMIO_NOTE(sc->platform, (o)) | if (sc->platform != NULL) \ | ||||
VIRTIO_MMIO_NOTE(sc->platform, (o)); \ | |||||
} while (0) | |||||
#define vtmmio_write_config_2(sc, o, v) \ | #define vtmmio_write_config_2(sc, o, v) \ | ||||
do { \ | |||||
bus_write_2((sc)->res[0], (o), (v)); \ | bus_write_2((sc)->res[0], (o), (v)); \ | ||||
VIRTIO_MMIO_NOTE(sc->platform, (o)) | if (sc->platform != NULL) \ | ||||
VIRTIO_MMIO_NOTE(sc->platform, (o)); \ | |||||
} while (0) | |||||
#define vtmmio_write_config_4(sc, o, v) \ | #define vtmmio_write_config_4(sc, o, v) \ | ||||
do { \ | |||||
bus_write_4((sc)->res[0], (o), (v)); \ | bus_write_4((sc)->res[0], (o), (v)); \ | ||||
VIRTIO_MMIO_NOTE(sc->platform, (o)) | if (sc->platform != NULL) \ | ||||
VIRTIO_MMIO_NOTE(sc->platform, (o)); \ | |||||
} while (0) | |||||
#define vtmmio_read_config_1(sc, o) \ | #define vtmmio_read_config_1(sc, o) \ | ||||
bus_read_1((sc)->res[0], (o)) | bus_read_1((sc)->res[0], (o)) | ||||
#define vtmmio_read_config_2(sc, o) \ | #define vtmmio_read_config_2(sc, o) \ | ||||
bus_read_2((sc)->res[0], (o)) | bus_read_2((sc)->res[0], (o)) | ||||
#define vtmmio_read_config_4(sc, o) \ | #define vtmmio_read_config_4(sc, o) \ | ||||
bus_read_4((sc)->res[0], (o)) | bus_read_4((sc)->res[0], (o)) | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | |||||
vtmmio_setup_intr(device_t dev, enum intr_type type) | vtmmio_setup_intr(device_t dev, enum intr_type type) | ||||
{ | { | ||||
struct vtmmio_softc *sc; | struct vtmmio_softc *sc; | ||||
int rid; | int rid; | ||||
int err; | int err; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
if (sc->platform != NULL) { | |||||
err = VIRTIO_MMIO_SETUP_INTR(sc->platform, sc->dev, | err = VIRTIO_MMIO_SETUP_INTR(sc->platform, sc->dev, | ||||
vtmmio_vq_intr, sc); | vtmmio_vq_intr, sc); | ||||
if (err == 0) { | if (err == 0) { | ||||
/* Okay we have backend-specific interrupts */ | /* Okay we have backend-specific interrupts */ | ||||
return (0); | return (0); | ||||
} | } | ||||
} | |||||
rid = 0; | rid = 0; | ||||
sc->res[1] = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, | sc->res[1] = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, | ||||
RF_ACTIVE); | RF_ACTIVE); | ||||
if (!sc->res[1]) { | if (!sc->res[1]) { | ||||
device_printf(dev, "Can't allocate interrupt\n"); | device_printf(dev, "Can't allocate interrupt\n"); | ||||
return (ENXIO); | return (ENXIO); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 251 Lines • ▼ Show 20 Lines | |||||
static int | static int | ||||
vtmmio_alloc_virtqueues(device_t dev, int flags, int nvqs, | vtmmio_alloc_virtqueues(device_t dev, int flags, int nvqs, | ||||
struct vq_alloc_info *vq_info) | struct vq_alloc_info *vq_info) | ||||
{ | { | ||||
struct vtmmio_virtqueue *vqx; | struct vtmmio_virtqueue *vqx; | ||||
struct vq_alloc_info *info; | struct vq_alloc_info *info; | ||||
struct vtmmio_softc *sc; | struct vtmmio_softc *sc; | ||||
struct virtqueue *vq; | struct virtqueue *vq; | ||||
uint32_t size; | |||||
int idx, error; | int idx, error; | ||||
uint16_t size; | |||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
if (sc->vtmmio_nvqs != 0) | if (sc->vtmmio_nvqs != 0) | ||||
return (EALREADY); | return (EALREADY); | ||||
if (nvqs <= 0) | if (nvqs <= 0) | ||||
return (EINVAL); | return (EINVAL); | ||||
sc->vtmmio_vqs = malloc(nvqs * sizeof(struct vtmmio_virtqueue), | sc->vtmmio_vqs = malloc(nvqs * sizeof(struct vtmmio_virtqueue), | ||||
M_DEVBUF, M_NOWAIT | M_ZERO); | M_DEVBUF, M_NOWAIT | M_ZERO); | ||||
if (sc->vtmmio_vqs == NULL) | if (sc->vtmmio_vqs == NULL) | ||||
return (ENOMEM); | return (ENOMEM); | ||||
for (idx = 0; idx < nvqs; idx++) { | for (idx = 0; idx < nvqs; idx++) { | ||||
vqx = &sc->vtmmio_vqs[idx]; | vqx = &sc->vtmmio_vqs[idx]; | ||||
info = &vq_info[idx]; | info = &vq_info[idx]; | ||||
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_SEL, idx); | |||||
vtmmio_select_virtqueue(sc, idx); | vtmmio_select_virtqueue(sc, idx); | ||||
size = vtmmio_read_config_2(sc, VIRTIO_MMIO_QUEUE_NUM); | size = vtmmio_read_config_4(sc, VIRTIO_MMIO_QUEUE_NUM_MAX); | ||||
error = virtqueue_alloc(dev, idx, size, | error = virtqueue_alloc(dev, idx, size, | ||||
VIRTIO_MMIO_VRING_ALIGN, 0xFFFFFFFFUL, info, &vq); | VIRTIO_MMIO_VRING_ALIGN, 0xFFFFFFFFUL, info, &vq); | ||||
if (error) { | if (error) { | ||||
device_printf(dev, | device_printf(dev, | ||||
"cannot allocate virtqueue %d: %d\n", | "cannot allocate virtqueue %d: %d\n", | ||||
idx, error); | idx, error); | ||||
break; | break; | ||||
} | } | ||||
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_NUM, size); | |||||
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_ALIGN, | |||||
VIRTIO_MMIO_VRING_ALIGN); | |||||
#if 0 | #if 0 | ||||
device_printf(dev, "virtqueue paddr 0x%08lx\n", | device_printf(dev, "virtqueue paddr 0x%08lx\n", | ||||
(uint64_t)virtqueue_paddr(vq)); | (uint64_t)virtqueue_paddr(vq)); | ||||
#endif | #endif | ||||
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_PFN, | vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_PFN, | ||||
virtqueue_paddr(vq) >> PAGE_SHIFT); | virtqueue_paddr(vq) >> PAGE_SHIFT); | ||||
vqx->vtv_vq = *info->vqai_vq = vq; | vqx->vtv_vq = *info->vqai_vq = vq; | ||||
▲ Show 20 Lines • Show All 53 Lines • ▼ Show 20 Lines | |||||
static void | static void | ||||
vtmmio_notify_virtqueue(device_t dev, uint16_t queue) | vtmmio_notify_virtqueue(device_t dev, uint16_t queue) | ||||
{ | { | ||||
struct vtmmio_softc *sc; | struct vtmmio_softc *sc; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
vtmmio_write_config_2(sc, VIRTIO_MMIO_QUEUE_NOTIFY, queue); | vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_NOTIFY, queue); | ||||
} | } | ||||
static uint8_t | static uint8_t | ||||
vtmmio_get_status(device_t dev) | vtmmio_get_status(device_t dev) | ||||
{ | { | ||||
struct vtmmio_softc *sc; | struct vtmmio_softc *sc; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
return (vtmmio_read_config_1(sc, VIRTIO_MMIO_STATUS)); | return (vtmmio_read_config_4(sc, VIRTIO_MMIO_STATUS)); | ||||
} | } | ||||
static void | static void | ||||
vtmmio_set_status(device_t dev, uint8_t status) | vtmmio_set_status(device_t dev, uint8_t status) | ||||
{ | { | ||||
struct vtmmio_softc *sc; | struct vtmmio_softc *sc; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
if (status != VIRTIO_CONFIG_STATUS_RESET) | if (status != VIRTIO_CONFIG_STATUS_RESET) | ||||
status |= vtmmio_get_status(dev); | status |= vtmmio_get_status(dev); | ||||
vtmmio_write_config_1(sc, VIRTIO_MMIO_STATUS, status); | vtmmio_write_config_4(sc, VIRTIO_MMIO_STATUS, status); | ||||
} | } | ||||
static void | static void | ||||
vtmmio_read_dev_config(device_t dev, bus_size_t offset, | vtmmio_read_dev_config(device_t dev, bus_size_t offset, | ||||
void *dst, int length) | void *dst, int length) | ||||
{ | { | ||||
struct vtmmio_softc *sc; | struct vtmmio_softc *sc; | ||||
bus_size_t off; | bus_size_t off; | ||||
uint8_t *d; | uint8_t *d; | ||||
int size; | int size; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
off = VIRTIO_MMIO_CONFIG + offset; | off = VIRTIO_MMIO_CONFIG + offset; | ||||
for (d = dst; length > 0; d += size, off += size, length -= size) { | for (d = dst; length > 0; d += size, off += size, length -= size) { | ||||
#ifdef ALLOW_WORD_ALIGNED_ACCESS | |||||
if (length >= 4) { | if (length >= 4) { | ||||
size = 4; | size = 4; | ||||
*(uint32_t *)d = vtmmio_read_config_4(sc, off); | *(uint32_t *)d = vtmmio_read_config_4(sc, off); | ||||
} else if (length >= 2) { | } else if (length >= 2) { | ||||
size = 2; | size = 2; | ||||
*(uint16_t *)d = vtmmio_read_config_2(sc, off); | *(uint16_t *)d = vtmmio_read_config_2(sc, off); | ||||
} else { | } else | ||||
#endif | |||||
{ | |||||
size = 1; | size = 1; | ||||
*d = vtmmio_read_config_1(sc, off); | *d = vtmmio_read_config_1(sc, off); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
static void | static void | ||||
vtmmio_write_dev_config(device_t dev, bus_size_t offset, | vtmmio_write_dev_config(device_t dev, bus_size_t offset, | ||||
void *src, int length) | void *src, int length) | ||||
{ | { | ||||
struct vtmmio_softc *sc; | struct vtmmio_softc *sc; | ||||
bus_size_t off; | bus_size_t off; | ||||
uint8_t *s; | uint8_t *s; | ||||
int size; | int size; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
off = VIRTIO_MMIO_CONFIG + offset; | off = VIRTIO_MMIO_CONFIG + offset; | ||||
for (s = src; length > 0; s += size, off += size, length -= size) { | for (s = src; length > 0; s += size, off += size, length -= size) { | ||||
#ifdef ALLOW_WORD_ALIGNED_ACCESS | |||||
if (length >= 4) { | if (length >= 4) { | ||||
size = 4; | size = 4; | ||||
vtmmio_write_config_4(sc, off, *(uint32_t *)s); | vtmmio_write_config_4(sc, off, *(uint32_t *)s); | ||||
} else if (length >= 2) { | } else if (length >= 2) { | ||||
size = 2; | size = 2; | ||||
vtmmio_write_config_2(sc, off, *(uint16_t *)s); | vtmmio_write_config_2(sc, off, *(uint16_t *)s); | ||||
} else { | } else | ||||
#endif | |||||
{ | |||||
size = 1; | size = 1; | ||||
vtmmio_write_config_1(sc, off, *s); | vtmmio_write_config_1(sc, off, *s); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
static void | static void | ||||
vtmmio_describe_features(struct vtmmio_softc *sc, const char *msg, | vtmmio_describe_features(struct vtmmio_softc *sc, const char *msg, | ||||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | vtmmio_reinit_virtqueue(struct vtmmio_softc *sc, int idx) | ||||
uint16_t size; | uint16_t size; | ||||
vqx = &sc->vtmmio_vqs[idx]; | vqx = &sc->vtmmio_vqs[idx]; | ||||
vq = vqx->vtv_vq; | vq = vqx->vtv_vq; | ||||
KASSERT(vq != NULL, ("%s: vq %d not allocated", __func__, idx)); | KASSERT(vq != NULL, ("%s: vq %d not allocated", __func__, idx)); | ||||
vtmmio_select_virtqueue(sc, idx); | vtmmio_select_virtqueue(sc, idx); | ||||
size = vtmmio_read_config_2(sc, VIRTIO_MMIO_QUEUE_NUM); | size = vtmmio_read_config_4(sc, VIRTIO_MMIO_QUEUE_NUM_MAX); | ||||
error = virtqueue_reinit(vq, size); | error = virtqueue_reinit(vq, size); | ||||
if (error) | if (error) | ||||
return (error); | return (error); | ||||
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_PFN, | vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_PFN, | ||||
virtqueue_paddr(vq) >> PAGE_SHIFT); | virtqueue_paddr(vq) >> PAGE_SHIFT); | ||||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | vtmmio_reset(struct vtmmio_softc *sc) | ||||
*/ | */ | ||||
vtmmio_set_status(sc->dev, VIRTIO_CONFIG_STATUS_RESET); | vtmmio_set_status(sc->dev, VIRTIO_CONFIG_STATUS_RESET); | ||||
} | } | ||||
static void | static void | ||||
vtmmio_select_virtqueue(struct vtmmio_softc *sc, int idx) | vtmmio_select_virtqueue(struct vtmmio_softc *sc, int idx) | ||||
{ | { | ||||
vtmmio_write_config_2(sc, VIRTIO_MMIO_QUEUE_SEL, idx); | vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_SEL, idx); | ||||
} | } | ||||
static void | static void | ||||
vtmmio_vq_intr(void *arg) | vtmmio_vq_intr(void *arg) | ||||
{ | { | ||||
struct vtmmio_virtqueue *vqx; | struct vtmmio_virtqueue *vqx; | ||||
struct vtmmio_softc *sc; | struct vtmmio_softc *sc; | ||||
struct virtqueue *vq; | struct virtqueue *vq; | ||||
uint32_t status; | |||||
int idx; | int idx; | ||||
sc = arg; | sc = arg; | ||||
status = vtmmio_read_config_4(sc, VIRTIO_MMIO_INTERRUPT_STATUS); | |||||
vtmmio_write_config_4(sc, VIRTIO_MMIO_INTERRUPT_ACK, status); | |||||
/* The config changed */ | |||||
if (status & VIRTIO_MMIO_INT_CONFIG) | |||||
if (sc->vtmmio_child_dev != NULL) | |||||
VIRTIO_CONFIG_CHANGE(sc->vtmmio_child_dev); | |||||
/* Notify all virtqueues. */ | /* Notify all virtqueues. */ | ||||
if (status & VIRTIO_MMIO_INT_VRING) { | |||||
for (idx = 0; idx < sc->vtmmio_nvqs; idx++) { | for (idx = 0; idx < sc->vtmmio_nvqs; idx++) { | ||||
vqx = &sc->vtmmio_vqs[idx]; | vqx = &sc->vtmmio_vqs[idx]; | ||||
if (vqx->vtv_no_intr == 0) { | |||||
vq = vqx->vtv_vq; | vq = vqx->vtv_vq; | ||||
virtqueue_intr(vq); | virtqueue_intr(vq); | ||||
}; | } | ||||
} | |||||
} | |||||
} | } |