Created
July 5, 2016 16:41
-
-
Save dmgolubovsky/a9cc5516aa9b6dbbeda35a8757ca3cbc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* This file is part of the Harvey operating system. It is subject to the | |
* license terms of the GNU GPL v2 in LICENSE.gpl found in the top-level | |
* directory of this distribution and at http://www.gnu.org/licenses/gpl-2.0.txt | |
* | |
* No part of Harvey operating system, including this file, may be copied, | |
* modified, propagated, or distributed except according to the terms | |
* contained in the LICENSE.gpl file. | |
*/ | |
#include "u.h" | |
#include "../port/lib.h" | |
#include "mem.h" | |
#include "dat.h" | |
#include "fns.h" | |
#include "io.h" | |
#include "../port/error.h" | |
#include "virtio_def.h" | |
// Include the definitions from VIRTIO spec v1.0 | |
// http://docs.oasis-open.org/virtio/virtio/v1.0/csprd02/listings/virtio_ring.h | |
#include "virtio_ring.h" | |
#include "virtio_config.h" | |
#include "virtio_pci.h" | |
#define TYPE(q) (q.path) | |
#define QID(c, t) (((c)<<4)|(t)) | |
#define BY2PG PGSZ | |
// Qids: 0 for top directory, 1 for the parent of all host mounts, 2 + device index for all host mounts. | |
enum { | |
Qtopdir = 0, | |
Qhostdir, | |
Qfstdev, | |
}; | |
static Dirtab topdir[] = { | |
".", { Qtopdir, 0, QTDIR }, 0, DMDIR|0555, | |
"hostfs", { Qhostdir, 0, QTDIR }, 0, DMDIR|0555, | |
}; | |
extern Dev v9pdevtab; | |
// Specific device control structures. They are preallocated during the driver | |
// initialization and remain mainly constant during the kernel uptime. | |
static int nv9p; // number of the detected virtio9p devices | |
typedef struct V9pctl // per-device control structure | |
{ | |
Pcidev *pci; // PCI device descriptor | |
uint32_t port; // base I/O port for the legacy port-based interface | |
uint32_t feat; // host features | |
uint32_t nqs; // virt queues count | |
Virtq **vqs; // virt queues descriptors | |
char *mount_tag; // mount tag obtained from the device configuration | |
} V9pctl; | |
static V9pctl **cv9p; // array of device control structure pointers, length = nv9p | |
static int | |
v9pgen(Chan *c, char *d, Dirtab* dir, int i, int s, Dir *dp) | |
{ | |
Proc *up = externup(); | |
Qid q; | |
int t = TYPE(c->qid); | |
print("\nv9pgen: type %d, s %d\n", t, s); | |
switch(t){ | |
case Qtopdir: | |
if(s == DEVDOTDOT){ | |
q = (Qid){QID(0, Qtopdir), 0, QTDIR}; | |
snprint(up->genbuf, sizeof up->genbuf, "#%C", v9pdevtab.dc); | |
devdir(c, q, up->genbuf, 0, eve, DMDIR|0555, dp); | |
return 1; | |
} | |
return devgen(c, nil, topdir, nelem(topdir), s, dp); | |
case Qhostdir: | |
if(s == DEVDOTDOT){ | |
q = (Qid){QID(0, Qtopdir), 0, QTDIR}; | |
snprint(up->genbuf, sizeof up->genbuf, "#%C", v9pdevtab.dc); | |
devdir(c, q, up->genbuf, 0, eve, DMDIR|0555, dp); | |
return 1; | |
} | |
if(s >= nv9p) | |
return -1; | |
strncpy(up->genbuf, cv9p[s]->mount_tag, sizeof up->genbuf); | |
q = (Qid) {QID(0, Qfstdev + s), 0, QTDIR}; | |
devdir(c, q, up->genbuf, 0, eve, DMDIR|0555, dp); | |
return 1; | |
default: | |
return -1; | |
} | |
return -1; | |
} | |
static Chan* | |
v9pattach(char *spec) | |
{ | |
return devattach(v9pdevtab.dc, spec); | |
} | |
Walkqid* | |
v9pwalk(Chan* c, Chan *nc, char** name, int nname) | |
{ | |
print("v9pwalk %d -> %d\n", c?TYPE(c->qid):-1, nc?TYPE(nc->qid):-1); | |
return devwalk(c, nc, name, nname, (Dirtab *)0, 0, v9pgen); | |
} | |
static int32_t | |
v9pstat(Chan* c, uint8_t* dp, int32_t n) | |
{ | |
return devstat(c, dp, n, (Dirtab *)0, 0L, v9pgen); | |
} | |
static Chan* | |
v9popen(Chan *c, int omode) | |
{ | |
c = devopen(c, omode, (Dirtab*)0, 0, v9pgen); | |
switch(TYPE(c->qid)){ | |
default: | |
break; | |
} | |
return c; | |
} | |
static void | |
v9pclose(Chan* c) | |
{ | |
} | |
static int32_t | |
v9pread(Chan *c, void *va, int32_t n, int64_t offset) | |
{ | |
return -1; | |
} | |
static int32_t | |
v9pwrite(Chan *c, void *va, int32_t n, int64_t offset) | |
{ | |
return -1; | |
} | |
// Scan virtqueues for the given device. If the vqs argument is not nil then | |
// nvq is expected to contain the length of the array vqs points to. In this case | |
// populate the Virtq structures for each virtqueue found. Otherwise just return | |
// the number of virtqueues detected. The port argument contains the base port | |
// for the device being scanned. | |
// Portions of code are borrowed from the 9front virtio drivers. | |
// TODO: this function should be shared among all virtio drivers. Move it outside of this driver. | |
static int | |
findvqs(uint32_t port, int nvq, Virtq **vqs) | |
{ | |
int cnt = 0; | |
while(1) { | |
outs(port + VIRTIO_PCI_QUEUE_SEL, cnt); | |
int qs = ins(port + VIRTIO_PCI_QUEUE_NUM); | |
print("\nv9p queue %d size %d\n", cnt, qs); | |
if(qs == 0 || (qs & (qs-1)) != 0) | |
break; | |
if(vqs != nil) { | |
// Allocate vq's descriptor space, used and available spaces, all page-aligned. | |
vqs[cnt] = malloc(sizeof(Virtq)); | |
Virtq *q = vqs[cnt]; | |
q->num = qs; | |
q->desc = mallocalign(PGROUND((q->num * sizeof(struct virtq_desc))), BY2PG, 0, 0); | |
q->free = -1; | |
q->nfree = qs; | |
// So in 9front: each descriptor's free field points to the next descriptor | |
for(int i = 0; i < qs; i++) { | |
q->desc[i].next = q->free; | |
q->free = i; | |
} | |
q->avail = mallocalign(PGROUND((sizeof(struct virtq_avail) + qs * sizeof(le16))), BY2PG, 0, 0); | |
q->used = mallocalign(PGROUND((sizeof(struct virtq_used) + qs * sizeof(le16))), BY2PG, 0, 0); | |
} | |
cnt++; | |
} | |
return cnt; | |
} | |
// Scan the PCI devices list for possible virtio9p devices. If the vcs argument | |
// is not nil then populate the array of control structures, otherwise just return | |
// the number of devices found. This function is intended to be called twice, | |
// once with vcs = nil just to count the devices, and the second time to populate | |
// the control structures, expecting vcs to point to an array of pointers to device | |
// descriptors of sufficient length. Portions of code are borrowed from the 9front virtio | |
// drivers. | |
static int | |
find9p(V9pctl **vcs) | |
{ | |
int cnt = 0; | |
// TODO: this seems to work as if MSI-X is not enabled (device conf space starts at 20). | |
// Find out how to deduce msix_enabled from the device. | |
int msix_enabled = 0; | |
Pcidev *p; | |
// Scan the collected PCI devices info, find possible 9p devices | |
for(p = nil; p = pcimatch(p, PCI_VENDOR_ID_REDHAT_QUMRANET, 0);) { | |
if(p->did != PCI_DEVICE_ID_VIRTIO_9P) | |
continue; | |
if(vcs != nil) { | |
vcs[cnt] = malloc(sizeof(V9pctl)); | |
if(vcs[cnt] == nil) { | |
print("\nv9p: failed to allocate device control structure for device %d\n", cnt); | |
return cnt; | |
} | |
// Use the legacy interface | |
// Allocate the BAR0 I/O space to the driver | |
V9pctl *vc = vcs[cnt]; | |
vc->pci = p; | |
vc->port = p->mem[0].bar & ~0x1; | |
print("\nv9p: device[%d] port %lux\n", cnt, vc->port); | |
if(ioalloc(vc->port, p->mem[0].size, 0, "virtio9p") < 0) { | |
print("\nv9p: port %lux in use\n", vc->port); | |
free(vc); | |
vcs[cnt] = nil; | |
return cnt; | |
} | |
// Device reset | |
outb(vc->port + VIRTIO_PCI_STATUS, 0); | |
vc->feat = inl(vc->port + VIRTIO_PCI_HOST_FEATURES); | |
print("\nv9p: features %08x\n"); | |
outb(vc->port + VIRTIO_PCI_STATUS, VIRTIO_CONFIG_S_ACKNOWLEDGE|VIRTIO_CONFIG_S_DRIVER); | |
int nqs = findvqs(vc->port, 0, nil); | |
print("\nv9p: found %d queues\n", nqs); | |
// For each vq allocate and populate its descriptor | |
if(nqs > 0) { | |
vc->vqs = malloc(nqs * sizeof(Virtq *)); | |
vc->nqs = nqs; | |
findvqs(vc->port, nqs, vc->vqs); | |
for(int i = 0; i < nqs; i++) { | |
Virtq *q = vc->vqs[i]; | |
q->idx = i; | |
q->pdev = vc; | |
coherence(); | |
outl(vc->port + VIRTIO_PCI_QUEUE_PFN, PADDR(q->desc)/BY2PG); | |
} | |
} | |
// Device config space contains mount tag supposedly in consecutive 8bit input ports | |
// Read the tag length from the 0-offset word (16bit) of the config space | |
int taglen = ins(vc->port + VIRTIO_PCI_CONFIG_OFF(msix_enabled)); | |
print("\nv9p: taglen %d\n", taglen); | |
// Allocate the space for mount tag, read byte by byte, 0-terminate | |
vc->mount_tag = malloc(taglen + 1); | |
memset(vc->mount_tag, 0, taglen + 1); | |
for(int i = 0; i < taglen; i++) { | |
vc->mount_tag[i] = inb(vc->port + VIRTIO_PCI_CONFIG_OFF(msix_enabled) + 2 + i); | |
} | |
print("\nv9p: mount tag %s\n", vc->mount_tag); | |
} | |
cnt++; | |
} | |
return cnt; | |
} | |
// Driver initialization: find all virtio9p devices (by vendor & device ID). | |
// Sense the device configuration, figure out mount tags. We assume that the | |
// virtio9p devices do not appear or disappear between reboots. | |
void | |
v9pinit(void) | |
{ | |
print_func_entry(); | |
print("\nv9p initializing\n"); | |
nv9p = find9p(nil); | |
print("\nv9p: found %d devices\n", nv9p); | |
if(nv9p == 0) { | |
return; | |
} | |
cv9p = malloc(nv9p * sizeof(V9pctl *)); | |
if(cv9p == nil) { | |
print("\nv9p: failed to allocate control structures\n"); | |
return; | |
} | |
find9p(cv9p); | |
} | |
Dev v9pdevtab = { | |
.dc = 'J', | |
.name = "v9p", | |
.reset = devreset, | |
.init = v9pinit, | |
.shutdown = devshutdown, | |
.attach = v9pattach, | |
.walk = v9pwalk, | |
.stat = v9pstat, | |
.open = v9popen, | |
.create = devcreate, | |
.close = v9pclose, | |
.read = v9pread, | |
.bread = devbread, | |
.write = v9pwrite, | |
.bwrite = devbwrite, | |
.remove = devremove, | |
.wstat = devwstat, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment