Browse Source

First portion of the virtconsole driver. Initialize device, set
features, allocate virtqueues. Some additions to virtio_lib.

Signed-off-by: golubovsky <golubovsky@gmail.com>

golubovsky 7 years ago
parent
commit
bafd0b04d6

+ 37 - 0
sys/include/virtio_lib.h

@@ -59,6 +59,43 @@ void reldescr(Virtq *q, int n, uint16_t *descr);
 
 int initvdevs(Vqctl **vcs);
 
+int vqalloc(Virtq **pq, int qs);
+
 void finalinitvdev(Vqctl *vc);
 
+int readvdevcfg(Vqctl *vc, void *va, int32_t n, int64_t offset);
+
+Vqctl *vdevbyidx(uint32_t idx);
+
+uint32_t vdevfeat(Vqctl *vc, uint32_t(*ffltr)(uint32_t));
+
+uint32_t getvdevnum(void);
+
+uint32_t getvdevsbypciid(int pciid, Vqctl **vqs, uint32_t n);
+
 static inline struct vring_desc * q2descr(Virtq *q, int i) { return q->vr.desc + i; }
+
+// Unified QID conversions between values and device/queue indices. We allocate bits:
+// 0 - 7 for QID type (specific to each driver)
+// 16 - 27 for device index (to use with vdevbyidx)
+// 32 - 63 for queue index within device
+
+// Extract QID type
+
+#define TYPE(q)			((uint32_t)(q).path & 0xFF)
+
+// Extract device index
+
+#define DEV(q)			((uint32_t)(((q).path >> 4) & 0x0FFF))
+
+// Extract queue index
+
+#define VQ(q)			((uint32_t)(((q).path >> 16) & 0x0FFFF))
+
+// Construct a non-queue aware QID (to address a per-device file)
+
+#define QID(c, t)		((((c) & 0x0FFF)<<4) | ((t) & 0xFF))
+
+// Construct a queue-aware QID (to address a per-queue file)
+
+#define VQQID(q, c, t)	((((q) & 0x0FFFF)<<16) | (((c) & 0x0FFF)<<4) | ((t) & 0x0F))

+ 6 - 0
sys/src/9/386/pci.c

@@ -19,6 +19,8 @@
 
 #include "io.h"
 
+void virtiosetup();
+
 int
 pcicapoff(Pcidev *p);
 
@@ -414,6 +416,10 @@ pcicfginit(void)
 	pcireservemem();
 	unlock(&pcicfginitlock);
 
+	// Bring the virtio devices live.
+	
+	virtiosetup();
+
 	//if(getconf("*pcihinv"))
 	pcihinv(nil);
 }

+ 1 - 0
sys/src/9/amd64/build.json

@@ -48,6 +48,7 @@
 					"uart",
 					"ws",
 					"usb",
+					"vcon",
 					"vga"
 				],
 				"Ip": [

+ 81 - 0
sys/src/9/port/devvcon.c

@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+// devvcon.c ('#C'): a virtual console (virtio-serial-pci) driver.
+
+#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_ring.h"
+
+#include	"virtio_config.h"
+#include	"virtio_console.h"
+#include	"virtio_pci.h"
+
+#include	"virtio_lib.h"
+
+// Array of defined virtconsoles and their number
+
+static uint32_t nvcon;
+
+static Vqctl **vcons;
+
+static void
+vconinit(void)
+{
+	uint32_t wantfeat(uint32_t f) {
+		return VIRTIO_CONSOLE_F_SIZE;	// We want only console size, but not multiport for simplicity
+	}
+	print("virtio-serial-pci initializing\n");
+	uint32_t nvdev = getvdevnum();
+	vcons = mallocz(nvdev * sizeof(Vqctl *), 1);
+	if(vcons == nil) {
+		print("no memory to allocate virtual consoles\n");
+		return;
+	}
+	nvcon = getvdevsbypciid(PCI_DEVICE_ID_VIRTIO_CONSOLE, vcons, nvdev);
+	print("virtio consoles found: %d\n", nvcon);
+	for(int i = 0; i < nvcon; i++) {
+		print("initializing virtual console %d\n", i);
+		uint32_t feat = vdevfeat(vcons[i], wantfeat);
+		print("features: 0x%08x\n", feat);
+		struct virtio_console_config vcfg;
+		int rc = readvdevcfg(vcons[i], &vcfg, sizeof(vcfg), 0);
+		print("config area size %d\n", rc);
+		print("cols=%d rows=%d ports=%d\n", vcfg.cols, vcfg.rows, vcfg.max_nr_ports);
+		finalinitvdev(vcons[i]);
+	}
+}
+
+Dev vcondevtab = {
+	.dc = 'C',
+	.name = "vcon",
+
+	.reset = devreset,
+	.init = vconinit,
+	.shutdown = devshutdown,
+//	.attach = vconattach,
+//	.walk = vconwalk,
+//	.stat = vconstat,
+//	.open = vconopen,
+	.create = devcreate,
+//	.close = vconclose,
+//	.read = vconread,
+	.bread = devbread,
+//	.write = vconwrite,
+	.bwrite = devbwrite,
+	.remove = devremove,
+	.wstat = devwstat,
+};

+ 1 - 0
sys/src/9/port/port.json

@@ -40,6 +40,7 @@
 			"../port/devtab.c",
 			"../port/devtrace.c",
 			"../port/devuart.c",
+			"../port/devvcon.c",
 			"../port/devwd.c",
 			"../port/devws.c",
 			"../port/edf.c",

+ 189 - 17
sys/src/9/port/virtio_lib.c

@@ -26,7 +26,56 @@
 
 #include	"virtio_lib.h"
 
-#define MAXVQS 8 			// maximal number of VQs per device
+#define MAXVQS 8 			// maximal detectable number of VQs per device
+
+static uint32_t nvq;		// number of the detected virtio9p devices
+
+static Vqctl **cvq;			// array of device control structure pointers, length = nvq
+
+// Map device identifiers to descriptive strings to display in IO port
+// and interrupt allocation maps.
+
+typedef struct
+{
+	uint16_t did;
+	char *desc;
+} didmap;
+
+static didmap dmtab[] = {
+	PCI_DEVICE_ID_VIRTIO_NET, "virtio-net",
+	PCI_DEVICE_ID_VIRTIO_BLOCK, "virtio-block",
+	PCI_DEVICE_ID_VIRTIO_BALLOON, "virtio-balloon",
+	PCI_DEVICE_ID_VIRTIO_CONSOLE, "virtio-console",
+	PCI_DEVICE_ID_VIRTIO_SCSI, "virtio-scsi",
+	PCI_DEVICE_ID_VIRTIO_RNG, "virtio-rng",
+	PCI_DEVICE_ID_VIRTIO_9P, "virtio-9p"
+};
+
+// Find a device type by its PCI device identifier, used to assign device name in the filesystem,
+// and to determine the flavor of read-write operations.
+
+static didmap *
+finddev(Vqctl *vc)
+{
+	for(int i = 0; i < nelem(dmtab) ; i++) {
+		if(vc->pci->did == dmtab[i].did) {
+			return (dmtab + i);
+		}
+	}
+	return nil;
+}
+
+// Map PCI device identifier to a readable name for the filesystem entry.
+
+static char *
+mapdev(Vqctl *vc)
+{
+	char *dmap = nil;
+	didmap *dm = finddev(vc);
+	if(dm != nil)
+		dmap = dm->desc;
+	return dmap;
+}
 
 static int
 viodone(void *arg)
@@ -169,6 +218,32 @@ queuedescr(Virtq *q, int n, uint16_t *descr)
 	return 0;
 }
 
+// Allocate space for a single queue and initialize its descriptor. This is normally called at startup
+// for every device's every queue discovered. It may however be necessary to process virtqueue hotplug
+// events as with virtio-console, so this procedure can be called independently.
+
+int
+vqalloc(Virtq **pq, int qs)
+{
+	*pq = mallocz(sizeof(Virtq) + qs * sizeof(Rock *), 1);
+	if(*pq == nil)
+		return -1;
+	Virtq *q = *pq;
+	uint64_t vrsize = vring_size(qs, PGSZ);
+	q->vq = mallocalign(vrsize, PGSZ, 0, 0);
+	if(q->vq == nil)
+		return -1;
+	memset(q->vq, 0, vrsize);
+	vring_init(&q->vr, qs, q->vq, PGSZ);
+	q->free = -1;
+	q->nfree = qs;
+	for(int i = 0; i < qs; i++) {
+		q->vr.desc[i].next = q->free;
+		q->free = i;
+	}
+	return 0;
+}
+
 // 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
@@ -188,20 +263,12 @@ findvqs(uint32_t port, int nvq, Virtq **vqs)
 			break;
 		if(vqs != nil) {
 			// Allocate vq's descriptor space, used and available spaces, all page-aligned.
-			vqs[cnt] = mallocz(sizeof(Virtq) + qs * sizeof(Rock *), 1);
-			uint64_t vrsize = vring_size(qs, PGSZ);
-			Virtq *q = vqs[cnt];
-			q->vq = mallocalign(vrsize, PGSZ, 0, 0);
-			memset(q->vq, 0, vrsize);
-			vring_init(&q->vr, qs, q->vq, PGSZ);
-			q->free = -1;
-			q->nfree = qs;
-			for(int i = 0; i < qs; i++) {
-				q->vr.desc[i].next = q->free;
-				q->free = i;
+			if(vqalloc(&vqs[cnt], qs) < 0) {
+				print("no memory to allocate a virtqueue\n");
+				break;
 			}
 			coherence();
-			uint64_t paddr=PADDR(q->vq);
+			uint64_t paddr=PADDR(vqs[cnt]->vq);
 			outl(port + VIRTIO_PCI_QUEUE_PFN, paddr/PGSZ);
 		}
 		cnt++;
@@ -236,7 +303,8 @@ initvdevs(Vqctl **vcs)
 			Vqctl *vc = vcs[cnt];
 			vc->pci = p;
 			vc->port = p->mem[0].bar & ~0x1;
-			snprint(vc->devname, sizeof(vc->devname), "virtio-pci-%d", cnt);
+			char *dmap = mapdev(vc);
+			snprint(vc->devname, sizeof(vc->devname), "%s-%d", dmap?dmap:"virtio-pci", cnt);
 			if(ioalloc(vc->port, p->mem[0].size, 0, vc->devname) < 0) {
 				free(vc);
 				vcs[cnt] = nil;
@@ -244,7 +312,6 @@ initvdevs(Vqctl **vcs)
 			}
 			// Device reset
 			outb(vc->port + VIRTIO_PCI_STATUS, 0);
-			vc->feat = inl(vc->port + VIRTIO_PCI_HOST_FEATURES);
 			outb(vc->port + VIRTIO_PCI_STATUS, VIRTIO_CONFIG_S_ACKNOWLEDGE|VIRTIO_CONFIG_S_DRIVER);
 			int nqs = findvqs(vc->port, 0, nil);
 			// For each vq allocate and populate its descriptor
@@ -269,9 +336,35 @@ initvdevs(Vqctl **vcs)
 	return cnt;
 }
 
-// Final device initialization. Enable interrupts, finish virtio features negotiation.
+// Identity finction for device features.
+
+static uint32_t 
+acceptallfeat(uint32_t feat)
+{
+	return feat;
+}
+
+// Negotiate on device features. Read in the features bitmap, alter as needed by the function
+// provided, write back to the device. If nil is provided as the function, write back unchanged
+// that is, accept whatever is offered (often nothing). Return the feature bits accepted, store
+// the same in the device control structure.
+
+uint32_t
+vdevfeat(Vqctl *vc, uint32_t(*ffltr)(uint32_t))
+{
+	uint32_t feat = inl(vc->port + VIRTIO_PCI_HOST_FEATURES);
+	uint32_t rfeat = ffltr?(*ffltr)(feat):acceptallfeat(feat);
+	rfeat &= feat;					// do not introduce new bits, we can only reject existing
+	vc->feat = rfeat;
+	outl(vc->port + VIRTIO_PCI_GUEST_FEATURES, rfeat);
+	return rfeat;
+}
+
+// Final device initialization, enable interrupts.
 // While initvdevs should be called once for all devices during the OS startup, finalinitdev
-// should be called once per device, from the device-specific part of the driver.
+// should be called once per device, from the device-specific part of the driver. If the driver
+// needs other interrupt handler than the default one, this function should not be called, and
+// custom logic should be provided instead.
 
 void
 finalinitvdev(Vqctl *vc)
@@ -279,3 +372,82 @@ finalinitvdev(Vqctl *vc)
 			intrenable(vc->pci->intl, vqintr, vc, vc->pci->tbdf, vc->devname);
 			outb(vc->port + VIRTIO_PCI_STATUS, inb(vc->port + VIRTIO_PCI_STATUS) | VIRTIO_CONFIG_S_DRIVER_OK);
 }
+
+// Read device configuration area into the given buffer at the given offset in the area.
+// Returned is number of bytes actually read. Reading is performed byte by byte, so endianness
+// is preserved. The program that reads the configuration area should take care of endianness conversion.
+
+int
+readvdevcfg(Vqctl *vc, void *va, int32_t n, int64_t offset)
+{
+	int8_t *a = va;
+	uint32_t r = offset;
+	int i;
+	for(i = 0; i < n; a++, i++) {
+		if(i + r >= vc->dcfglen)
+			break;
+		uint8_t b = inb(vc->port + vc->dcfgoff + i + r);
+		PBIT8(a, b);
+	}
+	return i;
+}
+
+// Initialize virtio globally (to be called once during startup).
+
+void
+virtiosetup()
+{
+	if(nvq != 0 || cvq != nil) 
+		return;						// avoid repeated calls
+	print("virtio: initializing\n");
+	nvq = initvdevs(nil);
+	if(nvq == 0) {
+		nvq = -1;
+		return;						// nothing found
+	}
+	cvq = mallocz(nvq * sizeof(Vqctl *), 1);
+	if(cvq == nil) {
+		print("virtiosetup: failed to allocate control structures\n");
+		nvq = -1;
+		return;
+	}
+	initvdevs(cvq);
+	print("virtio: initialized\n");
+}
+
+// Get pointer to a virtio device by its index. Nil is returned if idx is out of range.
+
+Vqctl *
+vdevbyidx(uint32_t idx)
+{
+	if(idx >= nvq)
+		return nil;
+	return cvq[idx];
+}
+
+// Get total number of virtio devices defined at the moment.
+
+uint32_t
+getvdevnum(void)
+{
+	return nvq;
+}
+
+// Find all devices of given type (e. g. PCI_DEVICE_ID_VIRTIO_NET). An array of sufficient length
+// should be provided; it will be filled out with the device references found. Returned is the number
+// of devices found.
+
+uint32_t
+getvdevsbypciid(int pciid, Vqctl **vqs, uint32_t n)
+{
+	uint32_t j = 0;
+	if(n < 1)
+		return 0;
+	for(int i = 0; i < nvq ; i++) {
+		if(cvq[i]->pci->did == pciid)
+			vqs[j++] = cvq[i];
+		if(j >= n)
+			break;
+	}
+	return j;
+}