Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
This commit is contained in:
commit
d0f7acf9eb
275 changed files with 51417 additions and 0 deletions
1365
kernel/drivers/bus/xhci/xhci.c
Normal file
1365
kernel/drivers/bus/xhci/xhci.c
Normal file
File diff suppressed because it is too large
Load diff
95
kernel/drivers/gpu/vga/vga_pci.c
Normal file
95
kernel/drivers/gpu/vga/vga_pci.c
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// Display controller -- the first driver to find its hardware by asking.
|
||||
//
|
||||
// It does not draw anything: kernel/dev/vga.c already owns the framebuffer,
|
||||
// and it learned where that is from the bootloader's VBE call. What this adds
|
||||
// is a second, independent answer to the same question, from the bus rather
|
||||
// than from the BIOS -- and a complaint when the two disagree.
|
||||
//
|
||||
// That is worth more than it sounds. Three places in the kernel used to
|
||||
// hardcode 0xfd000000 as the framebuffer address, and the only reason nobody
|
||||
// noticed was that QEMU's stdvga happens to put it there. A disagreement
|
||||
// between VBE and the card's own BAR is exactly the symptom that would have
|
||||
// shown up first on hardware where the guess was wrong, and until now there
|
||||
// was nothing in the kernel capable of noticing it.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "x86.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "pci.h"
|
||||
#include "display.h"
|
||||
|
||||
// The framebuffer lives in the first memory BAR big enough to hold a screen.
|
||||
// Display controllers commonly expose two apertures -- the framebuffer and a
|
||||
// much smaller register block -- and which index they land on varies, so the
|
||||
// choice is made by size rather than by position.
|
||||
static struct pci_bar *
|
||||
framebuffer_bar(struct pci_dev *d)
|
||||
{
|
||||
uint64 need = vga_fb_size();
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
struct pci_bar *b = &d->bar[i];
|
||||
if (b->base == 0 || b->is_io)
|
||||
continue;
|
||||
if (b->size >= need)
|
||||
return b;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
vga_pci_probe(struct pci_dev *d)
|
||||
{
|
||||
struct pci_bar *bar = framebuffer_bar(d);
|
||||
uint64 vbe = vga_fb_phys();
|
||||
|
||||
// Memory decoding has to be on for the aperture to respond at all. The BIOS
|
||||
// will have done this already to put up a boot splash, but a device we only
|
||||
// reached through enumeration cannot be assumed to have been touched.
|
||||
pci_enable(d, PCI_CMD_MEMORY);
|
||||
|
||||
if (bar == 0) {
|
||||
cprintf("vga: display controller at %x:%x.%x has no framebuffer aperture\n",
|
||||
d->bus, d->slot, d->func);
|
||||
return 0; // still ours; nothing more to do
|
||||
}
|
||||
|
||||
if (vbe != 0 && bar->base != vbe) {
|
||||
// Trust VBE: it describes the mode that is actually programmed and being
|
||||
// displayed, which is the thing vga.c is writing into. The BAR is the
|
||||
// card's whole aperture and the mode may sit at an offset within it. Say
|
||||
// so rather than silently preferring one.
|
||||
cprintf("vga: framebuffer disagreement -- VBE says %x, BAR says %x\n",
|
||||
(uint32)vbe, (uint32)bar->base);
|
||||
}
|
||||
|
||||
cprintf("vga: display controller %x:%x, aperture %x (%d MB)\n",
|
||||
d->vendor, d->device, (uint32)bar->base,
|
||||
(uint32)(bar->size / (1024 * 1024)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Matched by class, not by model: every VGA-compatible controller answers
|
||||
// class 0x03, and this driver cares about none of the differences between
|
||||
// them.
|
||||
static const struct pci_match vga_pci_match[] = {
|
||||
{ PCI_ANY, PCI_ANY, PCI_CLASS_DISPLAY, 0xFF },
|
||||
{ 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
// The display vtable lives in kernel/dev/vga.c, which owns the framebuffer;
|
||||
// this file only finds the card on the bus. Publishing it here is what makes
|
||||
// device_find_class(DEVCLASS_DISPLAY) return something a compositor can use.
|
||||
extern const struct display_ops vga_display_ops;
|
||||
|
||||
const struct pci_driver vga_pci_driver = {
|
||||
.name = "vga",
|
||||
.match = vga_pci_match,
|
||||
.ops = &vga_display_ops,
|
||||
.probe = vga_pci_probe,
|
||||
};
|
||||
435
kernel/drivers/gpu/virtio_gpu/virtio_gpu.c
Normal file
435
kernel/drivers/gpu/virtio_gpu/virtio_gpu.c
Normal file
|
|
@ -0,0 +1,435 @@
|
|||
// virtio-gpu, 2D. Written from the OASIS virtio 1.1 specification.
|
||||
//
|
||||
// This is the second implementation of the display class (include/display.h),
|
||||
// and the first one that is a real GPU driver rather than a flat framebuffer.
|
||||
// The difference that matters for a compositor: presenting is an explicit
|
||||
// operation. With VBE you write pixels and hope the scanout is not looking;
|
||||
// here you transfer a rectangle to the host and then flush it, and the host
|
||||
// does not show a half-drawn frame.
|
||||
//
|
||||
// Bring-up is the standard virtio 1.0 sequence and the order is not negotiable:
|
||||
// reset, ACKNOWLEDGE, DRIVER, read features, write features, FEATURES_OK, check
|
||||
// FEATURES_OK stuck, set up queues, DRIVER_OK. A device that finds the sequence
|
||||
// wrong is entitled to simply refuse, and it will not say why.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "x86.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "mmu.h"
|
||||
#include "memlayout.h"
|
||||
#include "pci.h"
|
||||
#include "device.h"
|
||||
#include "display.h"
|
||||
#include "dma.h"
|
||||
#include "virtio.h"
|
||||
|
||||
// One control queue is enough for 2D: there is also a cursor queue, which is
|
||||
// only worth having once there is a hardware cursor to move.
|
||||
#define CTRL_QUEUE 0
|
||||
|
||||
struct vgpu {
|
||||
volatile uint8 *common; // common configuration structure
|
||||
volatile uint8 *notify; // notification area
|
||||
uint32 notify_mult; // queue_notify_off multiplier
|
||||
|
||||
// The queue. Allocated as one contiguous block from the DMA arena, because
|
||||
// the device reads it by physical address and kalloc's pages are adjacent
|
||||
// only by chance.
|
||||
struct vring_desc *desc;
|
||||
struct vring_avail *avail;
|
||||
struct vring_used *used;
|
||||
uint16 qsize;
|
||||
uint16 notify_off;
|
||||
uint16 last_used;
|
||||
uint16 next_desc;
|
||||
|
||||
// A command buffer and a response buffer, both in DMA memory, reused for
|
||||
// every request. Only one command is ever in flight -- everything here is
|
||||
// called from process context during setup or from present(), and a GPU
|
||||
// command queue with one entry is not the bottleneck in a 2D compositor.
|
||||
void *cmd;
|
||||
void *resp;
|
||||
|
||||
int width, height;
|
||||
void *fb; // the framebuffer we scan out from
|
||||
uint32 fb_bytes;
|
||||
int ready;
|
||||
};
|
||||
|
||||
static struct vgpu gpu;
|
||||
|
||||
// --- MMIO accessors --------------------------------------------------------
|
||||
//
|
||||
// Explicit widths. The common configuration structure has 8-, 16- and 32-bit
|
||||
// fields side by side, and a device is entitled to ignore an access made at the
|
||||
// wrong width -- silently, with no error anywhere.
|
||||
|
||||
static inline uint8 r8 (volatile uint8 *p, int o) { return *(volatile uint8 *)(p + o); }
|
||||
static inline uint16 r16(volatile uint8 *p, int o) { return *(volatile uint16 *)(p + o); }
|
||||
static inline uint32 r32(volatile uint8 *p, int o) { return *(volatile uint32 *)(p + o); }
|
||||
static inline void w8 (volatile uint8 *p, int o, uint8 v) { *(volatile uint8 *)(p + o) = v; }
|
||||
static inline void w16(volatile uint8 *p, int o, uint16 v) { *(volatile uint16 *)(p + o) = v; }
|
||||
static inline void w32(volatile uint8 *p, int o, uint32 v) { *(volatile uint32 *)(p + o) = v; }
|
||||
|
||||
static void
|
||||
w64(volatile uint8 *p, int o, uint64 v)
|
||||
{
|
||||
// Two 32-bit halves, low first. The specification defines these fields as a
|
||||
// pair of 32-bit registers precisely so that a 32-bit driver can write them,
|
||||
// and some implementations latch on the high write.
|
||||
w32(p, o, (uint32)v);
|
||||
w32(p, o + 4, (uint32)(v >> 32));
|
||||
}
|
||||
|
||||
// --- capability walk -------------------------------------------------------
|
||||
|
||||
// Find a virtio configuration structure and map it.
|
||||
//
|
||||
// Each lives at an offset inside one of the device's BARs, and which BAR is not
|
||||
// fixed -- the device says so in a vendor-specific PCI capability. Assuming BAR
|
||||
// 0 works on QEMU today and is exactly the kind of assumption that stops
|
||||
// working on the next device.
|
||||
static volatile uint8 *
|
||||
find_cfg(struct pci_dev *d, int want, uint32 *out_mult)
|
||||
{
|
||||
uint8 off = pci_read8(d->bus, d->slot, d->func, 0x34) & 0xFC;
|
||||
|
||||
while (off) {
|
||||
uint8 id = pci_read8(d->bus, d->slot, d->func, off);
|
||||
uint8 next = pci_read8(d->bus, d->slot, d->func, off + 1);
|
||||
|
||||
if (id == PCI_CAP_ID_VNDR) {
|
||||
uint8 type = pci_read8(d->bus, d->slot, d->func, off + 3);
|
||||
uint8 bar = pci_read8(d->bus, d->slot, d->func, off + 4);
|
||||
uint32 o = pci_read32(d->bus, d->slot, d->func, off + 8);
|
||||
|
||||
if (type == want && bar < 6 && d->bar[bar].base) {
|
||||
if (out_mult) // only the notify capability has this trailing field
|
||||
*out_mult = pci_read32(d->bus, d->slot, d->func, off + 16);
|
||||
return (volatile uint8 *)P2V(d->bar[bar].base + o);
|
||||
}
|
||||
}
|
||||
off = next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- virtqueue -------------------------------------------------------------
|
||||
|
||||
static int
|
||||
queue_setup(struct vgpu *g)
|
||||
{
|
||||
w16(g->common, VCOMMON_Q_SELECT, CTRL_QUEUE);
|
||||
|
||||
uint16 qsize = r16(g->common, VCOMMON_Q_SIZE);
|
||||
if (qsize == 0)
|
||||
return -1;
|
||||
if (qsize > 64)
|
||||
qsize = 64; // more than we will ever have outstanding
|
||||
g->qsize = qsize;
|
||||
g->notify_off = r16(g->common, VCOMMON_Q_NOTIFY_OFF);
|
||||
|
||||
// The three rings, each aligned as the specification requires: 16 for the
|
||||
// descriptor table, 2 for available, 4 for used. Allocated as one block and
|
||||
// carved up, so there is one physical address to reason about.
|
||||
uint32 desc_sz = qsize * sizeof(struct vring_desc);
|
||||
uint32 avail_sz = 6 + 2 * qsize;
|
||||
uint32 used_sz = 6 + 8 * qsize;
|
||||
|
||||
uint8 *mem = dma_alloc(4096 * 3, 4096);
|
||||
if (!mem)
|
||||
return -1;
|
||||
memset(mem, 0, 4096 * 3);
|
||||
|
||||
g->desc = (struct vring_desc *) mem;
|
||||
g->avail = (struct vring_avail *)(mem + ((desc_sz + 1) & ~1U));
|
||||
g->used = (struct vring_used *)(mem + 4096); // its own page, 4-aligned
|
||||
(void)avail_sz; (void)used_sz;
|
||||
|
||||
w64(g->common, VCOMMON_Q_DESC, dma_phys(g->desc));
|
||||
w64(g->common, VCOMMON_Q_DRIVER, dma_phys(g->avail));
|
||||
w64(g->common, VCOMMON_Q_DEVICE, dma_phys(g->used));
|
||||
w16(g->common, VCOMMON_Q_ENABLE, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Submit one command and wait for the reply.
|
||||
//
|
||||
// Two descriptors: the request, which the device reads, and the response, which
|
||||
// it writes. Polled rather than interrupt-driven -- every caller is either boot
|
||||
// setup or present(), both of which want the answer before continuing, and an
|
||||
// interrupt would only add a sleep and a wakeup to a wait of a few microseconds.
|
||||
static int
|
||||
cmd(struct vgpu *g, uint32 cmdlen, uint32 resplen)
|
||||
{
|
||||
uint16 head = g->next_desc;
|
||||
uint16 second = (uint16)((head + 1) % g->qsize);
|
||||
|
||||
g->desc[head].addr = dma_phys(g->cmd);
|
||||
g->desc[head].len = cmdlen;
|
||||
g->desc[head].flags = VRING_DESC_F_NEXT;
|
||||
g->desc[head].next = second;
|
||||
|
||||
g->desc[second].addr = dma_phys(g->resp);
|
||||
g->desc[second].len = resplen;
|
||||
g->desc[second].flags = VRING_DESC_F_WRITE;
|
||||
g->desc[second].next = 0;
|
||||
|
||||
g->next_desc = (uint16)((second + 1) % g->qsize);
|
||||
|
||||
g->avail->ring[g->avail->idx % g->qsize] = head;
|
||||
__sync_synchronize(); // ring entry visible before idx
|
||||
g->avail->idx++;
|
||||
__sync_synchronize();
|
||||
|
||||
// Notify. The address is the notify region plus this queue's offset scaled by
|
||||
// the multiplier the device advertised; the value written is the queue index.
|
||||
*(volatile uint16 *)(g->notify + g->notify_off * g->notify_mult) = CTRL_QUEUE;
|
||||
|
||||
// Bounded, because a device that never answers must not hang the boot. At
|
||||
// QEMU's speed the reply is there almost immediately; on a wedged device this
|
||||
// returns an error that a caller can report.
|
||||
for (int i = 0; i < 10000000; i++) {
|
||||
if (g->used->idx != g->last_used) {
|
||||
g->last_used = g->used->idx;
|
||||
__sync_synchronize();
|
||||
struct virtio_gpu_ctrl_hdr *h = g->resp;
|
||||
return (h->type == VIRTIO_GPU_RESP_OK_NODATA ||
|
||||
h->type == VIRTIO_GPU_RESP_OK_DISPLAY_INFO) ? 0 : -1;
|
||||
}
|
||||
}
|
||||
cprintf("virtio-gpu: device did not answer\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
hdr_init(void *p, uint32 type)
|
||||
{
|
||||
struct virtio_gpu_ctrl_hdr *h = p;
|
||||
memset(h, 0, sizeof(*h));
|
||||
h->type = type;
|
||||
}
|
||||
|
||||
// --- the 2D sequence -------------------------------------------------------
|
||||
|
||||
static int
|
||||
setup_scanout(struct vgpu *g)
|
||||
{
|
||||
// What resolution is the display actually at? Asking is the point of
|
||||
// GET_DISPLAY_INFO; guessing 1024x768 is how a driver ends up working on
|
||||
// exactly one host configuration.
|
||||
hdr_init(g->cmd, VIRTIO_GPU_CMD_GET_DISPLAY_INFO);
|
||||
if (cmd(g, sizeof(struct virtio_gpu_ctrl_hdr),
|
||||
sizeof(struct virtio_gpu_resp_display_info)) != 0)
|
||||
return -1;
|
||||
|
||||
struct virtio_gpu_resp_display_info *info = g->resp;
|
||||
if (!info->pmodes[0].enabled || info->pmodes[0].r.width == 0) {
|
||||
cprintf("virtio-gpu: no enabled scanout\n");
|
||||
return -1;
|
||||
}
|
||||
g->width = (int)info->pmodes[0].r.width;
|
||||
g->height = (int)info->pmodes[0].r.height;
|
||||
|
||||
// A host resource to scan out from.
|
||||
{
|
||||
struct virtio_gpu_resource_create_2d *c = g->cmd;
|
||||
hdr_init(c, VIRTIO_GPU_CMD_RESOURCE_CREATE_2D);
|
||||
c->resource_id = 1;
|
||||
c->format = VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM;
|
||||
c->width = g->width;
|
||||
c->height = g->height;
|
||||
if (cmd(g, sizeof(*c), sizeof(struct virtio_gpu_ctrl_hdr)) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Guest memory to back it. This is the buffer the rest of the kernel draws
|
||||
// into; the device never reads it directly -- TRANSFER_TO_HOST_2D copies from
|
||||
// here into the host resource, which is what makes presenting explicit.
|
||||
g->fb_bytes = (uint32)g->width * g->height * 4;
|
||||
g->fb = dma_alloc(g->fb_bytes, 4096);
|
||||
if (!g->fb) {
|
||||
cprintf("virtio-gpu: no room for a %d KB framebuffer\n", g->fb_bytes / 1024);
|
||||
return -1;
|
||||
}
|
||||
memset(g->fb, 0, g->fb_bytes);
|
||||
|
||||
{
|
||||
struct virtio_gpu_resource_attach_backing *a = g->cmd;
|
||||
hdr_init(a, VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING);
|
||||
a->resource_id = 1;
|
||||
a->nr_entries = 1;
|
||||
struct virtio_gpu_mem_entry *e = (struct virtio_gpu_mem_entry *)(a + 1);
|
||||
e->addr = dma_phys(g->fb);
|
||||
e->length = g->fb_bytes;
|
||||
e->padding = 0;
|
||||
if (cmd(g, sizeof(*a) + sizeof(*e), sizeof(struct virtio_gpu_ctrl_hdr)) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Point scanout 0 at it.
|
||||
{
|
||||
struct virtio_gpu_set_scanout *s = g->cmd;
|
||||
hdr_init(s, VIRTIO_GPU_CMD_SET_SCANOUT);
|
||||
s->r.x = s->r.y = 0;
|
||||
s->r.width = g->width;
|
||||
s->r.height = g->height;
|
||||
s->scanout_id = 0;
|
||||
s->resource_id = 1;
|
||||
if (cmd(g, sizeof(*s), sizeof(struct virtio_gpu_ctrl_hdr)) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- display class ---------------------------------------------------------
|
||||
|
||||
static void
|
||||
vgpu_mode(struct device *d, struct display_mode *m)
|
||||
{
|
||||
m->width = gpu.width;
|
||||
m->height = gpu.height;
|
||||
m->bpp = 32;
|
||||
m->stride = (uint32)gpu.width * 4;
|
||||
}
|
||||
|
||||
static void *
|
||||
vgpu_acquire(struct device *d, uint32 *stride_out)
|
||||
{
|
||||
if (stride_out)
|
||||
*stride_out = (uint32)gpu.width * 4;
|
||||
// No separate back buffer: the guest-side framebuffer already *is* one. The
|
||||
// host cannot see it until TRANSFER_TO_HOST_2D, so drawing into it is
|
||||
// inherently off-screen -- which is the whole reason this device is better to
|
||||
// composite against than a raw VBE aperture.
|
||||
return gpu.ready ? gpu.fb : 0;
|
||||
}
|
||||
|
||||
static void
|
||||
vgpu_present(struct device *d, int x, int y, int w, int h)
|
||||
{
|
||||
if (!gpu.ready)
|
||||
return;
|
||||
if (x < 0) { w += x; x = 0; }
|
||||
if (y < 0) { h += y; y = 0; }
|
||||
if (x + w > gpu.width) w = gpu.width - x;
|
||||
if (y + h > gpu.height) h = gpu.height - y;
|
||||
if (w <= 0 || h <= 0)
|
||||
return;
|
||||
|
||||
{
|
||||
struct virtio_gpu_transfer_to_host_2d *t = gpu.cmd;
|
||||
hdr_init(t, VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D);
|
||||
t->r.x = x; t->r.y = y; t->r.width = w; t->r.height = h;
|
||||
t->offset = (uint64)y * gpu.width * 4 + (uint64)x * 4;
|
||||
t->resource_id = 1;
|
||||
if (cmd(&gpu, sizeof(*t), sizeof(struct virtio_gpu_ctrl_hdr)) != 0)
|
||||
return;
|
||||
}
|
||||
{
|
||||
struct virtio_gpu_resource_flush *f = gpu.cmd;
|
||||
hdr_init(f, VIRTIO_GPU_CMD_RESOURCE_FLUSH);
|
||||
f->r.x = x; f->r.y = y; f->r.width = w; f->r.height = h;
|
||||
f->resource_id = 1;
|
||||
cmd(&gpu, sizeof(*f), sizeof(struct virtio_gpu_ctrl_hdr));
|
||||
}
|
||||
}
|
||||
|
||||
// No wait_vblank. The device presents whole rectangles atomically, so there is
|
||||
// nothing to synchronise against -- and a null op is more honest than one that
|
||||
// spins and achieves nothing. display_wait_vblank() handles the absence.
|
||||
const struct display_ops virtio_gpu_display_ops = {
|
||||
.mode = vgpu_mode,
|
||||
.acquire = vgpu_acquire,
|
||||
.present = vgpu_present,
|
||||
.wait_vblank = 0,
|
||||
};
|
||||
|
||||
// --- probe -----------------------------------------------------------------
|
||||
|
||||
static int
|
||||
vgpu_probe(struct pci_dev *d)
|
||||
{
|
||||
struct vgpu *g = &gpu;
|
||||
|
||||
if (g->ready)
|
||||
return -1; // one display is enough for now
|
||||
|
||||
pci_enable(d, 0x0006); // memory space + bus master
|
||||
|
||||
g->common = find_cfg(d, VIRTIO_PCI_CAP_COMMON_CFG, 0);
|
||||
g->notify = find_cfg(d, VIRTIO_PCI_CAP_NOTIFY_CFG, &g->notify_mult);
|
||||
if (!g->common || !g->notify) {
|
||||
cprintf("virtio-gpu: no modern virtio capabilities; not a 1.0 device\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Reset, then the status handshake in the specified order.
|
||||
w8(g->common, VCOMMON_STATUS, 0);
|
||||
while (r8(g->common, VCOMMON_STATUS) != 0)
|
||||
;
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_ACK);
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_ACK | VSTATUS_DRIVER);
|
||||
|
||||
// Accept VIRTIO_F_VERSION_1 and nothing else. Feature bit 32 lives in the
|
||||
// second 32-bit window, which is what the select registers are for.
|
||||
w32(g->common, VCOMMON_DFEAT_SEL, 1);
|
||||
uint32 hi = r32(g->common, VCOMMON_DFEAT);
|
||||
if (!(hi & (1u << (VIRTIO_F_VERSION_1 - 32)))) {
|
||||
cprintf("virtio-gpu: device is not virtio 1.0\n");
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_FAILED);
|
||||
return -1;
|
||||
}
|
||||
w32(g->common, VCOMMON_GFEAT_SEL, 0);
|
||||
w32(g->common, VCOMMON_GFEAT, 0);
|
||||
w32(g->common, VCOMMON_GFEAT_SEL, 1);
|
||||
w32(g->common, VCOMMON_GFEAT, 1u << (VIRTIO_F_VERSION_1 - 32));
|
||||
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_ACK | VSTATUS_DRIVER | VSTATUS_FEATURES_OK);
|
||||
if (!(r8(g->common, VCOMMON_STATUS) & VSTATUS_FEATURES_OK)) {
|
||||
// The device is allowed to clear this bit to reject the feature set, and
|
||||
// this readback is the only way it ever tells you.
|
||||
cprintf("virtio-gpu: device refused our features\n");
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_FAILED);
|
||||
return -1;
|
||||
}
|
||||
|
||||
g->cmd = dma_alloc(4096, 16);
|
||||
g->resp = dma_alloc(4096, 16);
|
||||
if (!g->cmd || !g->resp || queue_setup(g) != 0) {
|
||||
cprintf("virtio-gpu: queue setup failed\n");
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_FAILED);
|
||||
return -1;
|
||||
}
|
||||
|
||||
w8(g->common, VCOMMON_STATUS,
|
||||
VSTATUS_ACK | VSTATUS_DRIVER | VSTATUS_FEATURES_OK | VSTATUS_DRIVER_OK);
|
||||
|
||||
if (setup_scanout(g) != 0) {
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_FAILED);
|
||||
return -1;
|
||||
}
|
||||
|
||||
g->ready = 1;
|
||||
cprintf("virtio-gpu: %dx%d, %d KB framebuffer, queue %d\n",
|
||||
g->width, g->height, g->fb_bytes / 1024, g->qsize);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pci_match virtio_gpu_match[] = {
|
||||
{ VIRTIO_VENDOR, VIRTIO_DEV_GPU, 0, 0 },
|
||||
{ 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
const struct pci_driver virtio_gpu_driver = {
|
||||
.name = "virtio-gpu",
|
||||
.match = virtio_gpu_match,
|
||||
.ops = &virtio_gpu_display_ops,
|
||||
.probe = vgpu_probe,
|
||||
};
|
||||
456
kernel/drivers/net/e1000/e1000.c
Normal file
456
kernel/drivers/net/e1000/e1000.c
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
// Intel 8254x gigabit Ethernet, written from Intel's public datasheet (317453).
|
||||
//
|
||||
// The first network device. There is no IP stack yet, so what this provides is
|
||||
// the layer under one: a MAC address, a transmit path, and a receive ring that
|
||||
// fills with frames. That is deliberately the right order -- an IP stack
|
||||
// written before a driver has nothing to be tested against, and every bug in it
|
||||
// looks like a driver bug.
|
||||
//
|
||||
// Bring-up follows the manual's initialisation sequence (section 14.3-14.5):
|
||||
// reset, read the MAC out of EEPROM, clear the multicast table, set up the
|
||||
// receive and transmit rings, then enable each. The order matters -- enabling a
|
||||
// receiver whose ring base is still zero makes the device DMA into physical
|
||||
// address 0, which on this kernel is the boot page table.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "x86.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "mmu.h"
|
||||
#include "memlayout.h"
|
||||
#include "pci.h"
|
||||
#include "device.h"
|
||||
#include "dma.h"
|
||||
#include "irq.h"
|
||||
#include "e1000.h"
|
||||
|
||||
struct e1000 {
|
||||
volatile uint8 *regs;
|
||||
|
||||
struct e1000_tx_desc *tx;
|
||||
struct e1000_rx_desc *rx;
|
||||
uint8 *txbuf[E1000_NTX];
|
||||
uint8 *rxbuf[E1000_NRX];
|
||||
uint16 tx_next;
|
||||
uint16 rx_next;
|
||||
|
||||
uint8 mac[6];
|
||||
int ready;
|
||||
uint64 rx_packets, tx_packets, rx_dropped;
|
||||
|
||||
struct spinlock lock;
|
||||
};
|
||||
|
||||
static struct e1000 nic;
|
||||
|
||||
static inline uint32 rd(struct e1000 *e, int off) { return *(volatile uint32 *)(e->regs + off); }
|
||||
static inline void wr(struct e1000 *e, int off, uint32 v) { *(volatile uint32 *)(e->regs + off) = v; }
|
||||
|
||||
// The EEPROM is read one 16-bit word at a time through a single register: write
|
||||
// the address with the start bit, poll for done, take the data out of the top
|
||||
// half.
|
||||
//
|
||||
// Bounded, because a device whose EEPROM never reports done would otherwise
|
||||
// hang the boot -- and QEMU's e1000 does answer, but a real card with a blank
|
||||
// EEPROM is a thing that exists.
|
||||
static int
|
||||
eeprom_read(struct e1000 *e, int addr, uint16 *out)
|
||||
{
|
||||
wr(e, E1000_EERD, (addr << E1000_EERD_ADDR_SHIFT) | E1000_EERD_START);
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
uint32 v = rd(e, E1000_EERD);
|
||||
if (v & E1000_EERD_DONE) {
|
||||
*out = (uint16)(v >> E1000_EERD_DATA_SHIFT);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The MAC address, from EEPROM words 0..2, little-endian within each word.
|
||||
//
|
||||
// Falls back to the receive-address registers, which the firmware may already
|
||||
// have programmed. QEMU fills both; some cards leave the EEPROM path
|
||||
// unimplemented, and a driver that only knows one of the two ends up with a MAC
|
||||
// of all zeroes and no idea why nothing answers it.
|
||||
static void
|
||||
read_mac(struct e1000 *e)
|
||||
{
|
||||
uint16 w;
|
||||
int ok = 1;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (eeprom_read(e, i, &w) != 0) { ok = 0; break; }
|
||||
e->mac[i * 2] = (uint8)(w & 0xFF);
|
||||
e->mac[i * 2 + 1] = (uint8)(w >> 8);
|
||||
}
|
||||
if (ok)
|
||||
return;
|
||||
|
||||
uint32 lo = rd(e, E1000_RAL), hi = rd(e, E1000_RAH);
|
||||
e->mac[0] = (uint8)lo; e->mac[1] = (uint8)(lo >> 8);
|
||||
e->mac[2] = (uint8)(lo >> 16); e->mac[3] = (uint8)(lo >> 24);
|
||||
e->mac[4] = (uint8)hi; e->mac[5] = (uint8)(hi >> 8);
|
||||
}
|
||||
|
||||
static int
|
||||
rings_setup(struct e1000 *e)
|
||||
{
|
||||
// One block for both rings and every buffer, from the DMA arena: the device
|
||||
// reads all of it by physical address, and kalloc's pages are contiguous only
|
||||
// by accident.
|
||||
uint32 txring = E1000_NTX * sizeof(struct e1000_tx_desc);
|
||||
uint32 rxring = E1000_NRX * sizeof(struct e1000_rx_desc);
|
||||
|
||||
e->tx = dma_alloc(txring, 16);
|
||||
e->rx = dma_alloc(rxring, 16);
|
||||
if (!e->tx || !e->rx)
|
||||
return -1;
|
||||
memset(e->tx, 0, txring);
|
||||
memset(e->rx, 0, rxring);
|
||||
|
||||
for (int i = 0; i < E1000_NTX; i++) {
|
||||
e->txbuf[i] = dma_alloc(E1000_BUFSZ, 16);
|
||||
if (!e->txbuf[i])
|
||||
return -1;
|
||||
e->tx[i].addr = dma_phys(e->txbuf[i]);
|
||||
// DD set on every transmit descriptor at start-up, so the first pass round
|
||||
// the ring sees them as free. The hardware only sets it after it has used
|
||||
// one, and a driver that waits for a bit the device has never had reason to
|
||||
// write waits forever.
|
||||
e->tx[i].status = E1000_TXD_STAT_DD;
|
||||
}
|
||||
|
||||
for (int i = 0; i < E1000_NRX; i++) {
|
||||
e->rxbuf[i] = dma_alloc(E1000_BUFSZ, 16);
|
||||
if (!e->rxbuf[i])
|
||||
return -1;
|
||||
e->rx[i].addr = dma_phys(e->rxbuf[i]);
|
||||
e->rx[i].status = 0;
|
||||
}
|
||||
|
||||
wr(e, E1000_TDBAL, (uint32)dma_phys(e->tx));
|
||||
wr(e, E1000_TDBAH, (uint32)(dma_phys(e->tx) >> 32));
|
||||
wr(e, E1000_TDLEN, txring);
|
||||
wr(e, E1000_TDH, 0);
|
||||
wr(e, E1000_TDT, 0);
|
||||
|
||||
wr(e, E1000_RDBAL, (uint32)dma_phys(e->rx));
|
||||
wr(e, E1000_RDBAH, (uint32)(dma_phys(e->rx) >> 32));
|
||||
wr(e, E1000_RDLEN, rxring);
|
||||
wr(e, E1000_RDH, 0);
|
||||
// Tail points at the last descriptor the hardware may fill. Setting it to
|
||||
// NRX-1 hands it every descriptor but one; the ring is full when head meets
|
||||
// tail, so one must always be held back or "full" and "empty" look identical.
|
||||
wr(e, E1000_RDT, E1000_NRX - 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Queue one frame. Returns 0, or -1 if the ring is full.
|
||||
//
|
||||
// Copies into the descriptor's own buffer rather than mapping the caller's.
|
||||
// A zero-copy path wants the caller to guarantee the memory stays put and is
|
||||
// physically contiguous, which nothing above this can promise yet.
|
||||
int
|
||||
e1000_transmit(const void *frame, int len)
|
||||
{
|
||||
struct e1000 *e = &nic;
|
||||
|
||||
if (!e->ready || len <= 0 || len > E1000_BUFSZ)
|
||||
return -1;
|
||||
|
||||
acquire(&e->lock);
|
||||
struct e1000_tx_desc *d = &e->tx[e->tx_next];
|
||||
if (!(d->status & E1000_TXD_STAT_DD)) {
|
||||
release(&e->lock);
|
||||
return -1; // the device has not finished with it
|
||||
}
|
||||
|
||||
memmove(e->txbuf[e->tx_next], frame, len);
|
||||
d->length = (uint16)len;
|
||||
d->cmd = E1000_TXD_CMD_EOP | E1000_TXD_CMD_IFCS | E1000_TXD_CMD_RS;
|
||||
d->status = 0;
|
||||
|
||||
e->tx_next = (uint16)((e->tx_next + 1) % E1000_NTX);
|
||||
__sync_synchronize(); // descriptor visible before the tail moves
|
||||
wr(e, E1000_TDT, e->tx_next);
|
||||
e->tx_packets++;
|
||||
release(&e->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Take one received frame, if there is one. Returns its length, or 0.
|
||||
//
|
||||
// Polled. There is nothing above this to wake yet, and a receive path that
|
||||
// wakes a nonexistent protocol stack is harder to reason about than one that is
|
||||
// asked. The interrupt handler below exists to keep the cause register clear
|
||||
// and to count; when an IP stack arrives, this is where the hand-off changes.
|
||||
int
|
||||
e1000_receive(void *out, int cap)
|
||||
{
|
||||
struct e1000 *e = &nic;
|
||||
|
||||
if (!e->ready)
|
||||
return 0;
|
||||
|
||||
acquire(&e->lock);
|
||||
struct e1000_rx_desc *d = &e->rx[e->rx_next];
|
||||
if (!(d->status & E1000_RXD_STAT_DD)) {
|
||||
release(&e->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int len = d->length;
|
||||
if (len > cap)
|
||||
len = cap;
|
||||
memmove(out, e->rxbuf[e->rx_next], len);
|
||||
|
||||
d->status = 0;
|
||||
__sync_synchronize();
|
||||
// Hand the descriptor back by moving the tail to it. The tail always trails
|
||||
// the descriptor just released, which is why it is the *previous* index.
|
||||
wr(e, E1000_RDT, e->rx_next);
|
||||
e->rx_next = (uint16)((e->rx_next + 1) % E1000_NRX);
|
||||
e->rx_packets++;
|
||||
release(&e->lock);
|
||||
return len;
|
||||
}
|
||||
|
||||
static void
|
||||
e1000_intr(void *arg)
|
||||
{
|
||||
struct e1000 *e = arg;
|
||||
// Reading ICR clears it. Not reading it leaves the cause asserted, and with a
|
||||
// level-triggered PCI interrupt that means the handler is re-entered forever.
|
||||
uint32 cause = rd(e, E1000_ICR);
|
||||
(void)cause;
|
||||
}
|
||||
|
||||
int
|
||||
e1000_info(char *buf, int cap)
|
||||
{
|
||||
struct e1000 *e = &nic;
|
||||
int n = 0;
|
||||
|
||||
if (!e->ready)
|
||||
return 0;
|
||||
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
n += 0;
|
||||
for (int i = 0; i < 6 && n + 3 < cap; i++) {
|
||||
buf[n++] = hex[e->mac[i] >> 4];
|
||||
buf[n++] = hex[e->mac[i] & 0xF];
|
||||
if (i < 5)
|
||||
buf[n++] = ':';
|
||||
}
|
||||
if (n < cap) buf[n++] = '\n';
|
||||
return n;
|
||||
}
|
||||
|
||||
static int
|
||||
e1000_probe(struct pci_dev *d)
|
||||
{
|
||||
struct e1000 *e = &nic;
|
||||
|
||||
if (e->ready)
|
||||
return -1;
|
||||
if (!d->bar[0].base)
|
||||
return -1;
|
||||
|
||||
initlock(&e->lock, "e1000");
|
||||
pci_enable(d, 0x0006); // memory space + bus master
|
||||
e->regs = (volatile uint8 *)P2V(d->bar[0].base);
|
||||
|
||||
// Reset, then wait for it to clear. The manual asks for a delay after reset
|
||||
// before any other register is touched; polling CTRL is that delay.
|
||||
wr(e, E1000_CTRL, rd(e, E1000_CTRL) | E1000_CTRL_RST);
|
||||
for (int i = 0; i < 1000000; i++)
|
||||
if (!(rd(e, E1000_CTRL) & E1000_CTRL_RST))
|
||||
break;
|
||||
|
||||
wr(e, E1000_IMC, 0xFFFFFFFF); // mask everything while we set up
|
||||
rd(e, E1000_ICR); // and clear whatever was pending
|
||||
|
||||
wr(e, E1000_CTRL, rd(e, E1000_CTRL) | E1000_CTRL_SLU | E1000_CTRL_ASDE);
|
||||
|
||||
read_mac(e);
|
||||
|
||||
// The multicast table is undefined after reset. Left as it comes up, the
|
||||
// device accepts multicast traffic for addresses nobody asked for.
|
||||
for (int i = 0; i < 128; i++)
|
||||
wr(e, E1000_MTA + i * 4, 0);
|
||||
|
||||
// Program our own MAC into the receive-address filter, with the valid bit.
|
||||
{
|
||||
uint32 lo = (uint32)e->mac[0] | ((uint32)e->mac[1] << 8) |
|
||||
((uint32)e->mac[2] << 16) | ((uint32)e->mac[3] << 24);
|
||||
uint32 hi = (uint32)e->mac[4] | ((uint32)e->mac[5] << 8) | (1u << 31);
|
||||
wr(e, E1000_RAL, lo);
|
||||
wr(e, E1000_RAH, hi);
|
||||
}
|
||||
|
||||
if (rings_setup(e) != 0) {
|
||||
cprintf("e1000: no DMA memory for the rings\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
wr(e, E1000_TIPG, 10 | (8 << 10) | (6 << 20)); // the manual's IEEE defaults
|
||||
wr(e, E1000_TCTL, E1000_TCTL_EN | E1000_TCTL_PSP |
|
||||
(0x10 << E1000_TCTL_CT_SHIFT) |
|
||||
(0x40 << E1000_TCTL_COLD_SHIFT));
|
||||
|
||||
wr(e, E1000_RCTL, E1000_RCTL_EN | E1000_RCTL_BAM | E1000_RCTL_SECRC |
|
||||
E1000_RCTL_SZ_2048 | E1000_RCTL_LBM_NONE);
|
||||
|
||||
if (d->irq != 0xFF)
|
||||
// level_low = 1: this is a PCI interrupt, which is level-triggered and
|
||||
// active low. Registering it as edge-triggered makes it fire once and then
|
||||
// wedge, because the device holds the line asserted and no further edge
|
||||
// ever arrives. See the comment on irq_register in include/irq.h.
|
||||
irq_register(d->irq, e1000_intr, e, "e1000", 1);
|
||||
wr(e, E1000_IMS, E1000_ICR_RXT0 | E1000_ICR_RXDMT0 | E1000_ICR_LSC);
|
||||
|
||||
e->ready = 1;
|
||||
|
||||
cprintf("e1000: %x:%x:%x:%x:%x:%x, link %s, irq %d\n",
|
||||
e->mac[0], e->mac[1], e->mac[2], e->mac[3], e->mac[4], e->mac[5],
|
||||
(rd(e, E1000_STATUS) & E1000_STATUS_LU) ? "up" : "down", d->irq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Every 8254x that shares the register map above. See include/e1000.h for
|
||||
// where the IDs come from and why the PCIe parts are excluded.
|
||||
#define M(id) { E1000_VENDOR, (id), 0, 0 }
|
||||
static const struct pci_match e1000_match[] = {
|
||||
M(E1000_DEV_82540EM), M(E1000_DEV_82540EM_LOM),
|
||||
M(E1000_DEV_82540EP_LOM), M(E1000_DEV_82540EP),
|
||||
M(E1000_DEV_82540EP_LP),
|
||||
M(E1000_DEV_82541EI), M(E1000_DEV_82541EI_MOBILE),
|
||||
M(E1000_DEV_82541ER_LOM), M(E1000_DEV_82541ER),
|
||||
M(E1000_DEV_82541GI), M(E1000_DEV_82541GI_MOBILE),
|
||||
M(E1000_DEV_82541GI_LF),
|
||||
M(E1000_DEV_82544EI_FIBER), M(E1000_DEV_82544GC_COPPER),
|
||||
M(E1000_DEV_82544GC_LOM),
|
||||
M(E1000_DEV_82545EM_COPPER), M(E1000_DEV_82545EM_FIBER),
|
||||
M(E1000_DEV_82545GM_COPPER), M(E1000_DEV_82545GM_FIBER),
|
||||
M(E1000_DEV_82545GM_SERDES),
|
||||
M(E1000_DEV_82546EB_COPPER), M(E1000_DEV_82546EB_FIBER),
|
||||
M(E1000_DEV_82546EB_QUAD),
|
||||
M(E1000_DEV_82546GB_COPPER), M(E1000_DEV_82546GB_FIBER),
|
||||
M(E1000_DEV_82546GB_SERDES),
|
||||
M(E1000_DEV_82547EI),
|
||||
{ 0, 0, 0, 0 },
|
||||
};
|
||||
#undef M
|
||||
|
||||
const struct pci_driver e1000_driver = {
|
||||
.name = "e1000",
|
||||
.devname = "eth",
|
||||
.match = e1000_match,
|
||||
.ops = 0, // no class vtable yet; there is no net class
|
||||
.probe = e1000_probe,
|
||||
};
|
||||
|
||||
// --- self-test: does this thing actually move packets? ---------------------
|
||||
//
|
||||
// The transmit and receive paths were written together and neither had ever
|
||||
// carried a frame. That is exactly the state in which a driver looks finished
|
||||
// and is not: the rings are set up, the registers read back plausibly, and the
|
||||
// first thing to discover otherwise would have been a protocol stack failing
|
||||
// for reasons indistinguishable from its own bugs.
|
||||
//
|
||||
// So: build an ARP request by hand, send it, and wait for the reply. It proves
|
||||
// the whole path at once -- descriptor format, DD handshake, tail bookkeeping,
|
||||
// DMA addresses, the receive ring handback, and the MAC filter -- because an
|
||||
// ARP reply is addressed to *us* and will not arrive if the filter is wrong.
|
||||
//
|
||||
// Under QEMU's user-mode networking the guest is 10.0.2.15 and the gateway is
|
||||
// 10.0.2.2. SLIRP is silent until spoken to, which is why this has to transmit
|
||||
// before it can expect to receive.
|
||||
//
|
||||
// `echo n > /proc/sysrq`.
|
||||
|
||||
static void
|
||||
put_be16(uint8 *p, uint16 v)
|
||||
{
|
||||
p[0] = (uint8)(v >> 8);
|
||||
p[1] = (uint8)v;
|
||||
}
|
||||
|
||||
const unsigned char *
|
||||
e1000_mac(void)
|
||||
{
|
||||
return nic.mac;
|
||||
}
|
||||
|
||||
void
|
||||
e1000_selftest(void)
|
||||
{
|
||||
struct e1000 *e = &nic;
|
||||
uint8 frame[42];
|
||||
uint8 reply[E1000_BUFSZ];
|
||||
|
||||
if (!e->ready) {
|
||||
cprintf("e1000: no device\n");
|
||||
return;
|
||||
}
|
||||
|
||||
static const uint8 me[4] = { 10, 0, 2, 15 }; // QEMU SLIRP guest address
|
||||
static const uint8 them[4] = { 10, 0, 2, 2 }; // and its gateway
|
||||
|
||||
memset(frame, 0, sizeof(frame));
|
||||
for (int i = 0; i < 6; i++)
|
||||
frame[i] = 0xFF; // broadcast
|
||||
for (int i = 0; i < 6; i++)
|
||||
frame[6 + i] = e->mac[i];
|
||||
put_be16(&frame[12], 0x0806); // ARP
|
||||
|
||||
put_be16(&frame[14], 1); // Ethernet
|
||||
put_be16(&frame[16], 0x0800); // IPv4
|
||||
frame[18] = 6; // hardware address length
|
||||
frame[19] = 4; // protocol address length
|
||||
put_be16(&frame[20], 1); // request
|
||||
for (int i = 0; i < 6; i++)
|
||||
frame[22 + i] = e->mac[i]; // sender hardware address
|
||||
for (int i = 0; i < 4; i++)
|
||||
frame[28 + i] = me[i]; // sender protocol address
|
||||
// target hardware address stays zero -- that is what is being asked
|
||||
for (int i = 0; i < 4; i++)
|
||||
frame[38 + i] = them[i];
|
||||
|
||||
cprintf("e1000: asking who has %d.%d.%d.%d\n", them[0], them[1], them[2], them[3]);
|
||||
if (e1000_transmit(frame, sizeof(frame)) != 0) {
|
||||
cprintf("e1000: transmit ring full\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bounded poll. There is no interrupt path to sleep on yet and this runs from
|
||||
// a sysrq write, so spinning briefly is the honest way to wait.
|
||||
for (int spin = 0; spin < 20000000; spin++) {
|
||||
int n = e1000_receive(reply, sizeof(reply));
|
||||
if (n <= 0)
|
||||
continue;
|
||||
|
||||
cprintf("e1000: %d bytes from %x:%x:%x:%x:%x:%x type %x\n",
|
||||
n, reply[6], reply[7], reply[8], reply[9], reply[10], reply[11],
|
||||
((uint32)reply[12] << 8) | reply[13]);
|
||||
|
||||
if (n >= 42 && reply[12] == 0x08 && reply[13] == 0x06 &&
|
||||
reply[20] == 0 && reply[21] == 2) {
|
||||
cprintf("e1000: ARP reply -- %d.%d.%d.%d is at %x:%x:%x:%x:%x:%x\n",
|
||||
reply[28], reply[29], reply[30], reply[31],
|
||||
reply[22], reply[23], reply[24], reply[25],
|
||||
reply[26], reply[27]);
|
||||
cprintf("e1000: self-test passed, %d tx %d rx\n",
|
||||
(int)e->tx_packets, (int)e->rx_packets);
|
||||
return;
|
||||
}
|
||||
}
|
||||
cprintf("e1000: no reply -- %d tx, %d rx\n",
|
||||
(int)e->tx_packets, (int)e->rx_packets);
|
||||
}
|
||||
114
kernel/drivers/registry.c
Normal file
114
kernel/drivers/registry.c
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
// The driver registry.
|
||||
//
|
||||
// One table, one line per driver. This is the same shape as
|
||||
// kernel/sys/table.inc and kernel/dev/cdev.c's devsw: a single place that
|
||||
// lists what exists, so a driver cannot be half-registered.
|
||||
//
|
||||
// It is a plain array rather than the linker-section trick a bigger kernel
|
||||
// would use (a __attribute__((section(".drivers"))) entry per driver, walked
|
||||
// between __start_ and __stop_ symbols). That approach lets a driver register
|
||||
// itself by existing, which is genuinely nicer -- but it also means the list
|
||||
// of what is in your kernel is not written down anywhere you can read, and
|
||||
// a driver silently dropped by the linker looks exactly like one that was
|
||||
// never written. At this size, being able to read the list wins.
|
||||
//
|
||||
// To add a driver: write kernel/drivers/<name>.c exporting a struct
|
||||
// pci_driver, declare it here, and add it to the array and to the Makefile's
|
||||
// KERN_DRIVERS.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "pci.h"
|
||||
|
||||
#include "usb.h"
|
||||
#include "device.h"
|
||||
|
||||
extern const struct pci_driver vga_pci_driver;
|
||||
extern const struct pci_driver xhci_driver;
|
||||
extern const struct pci_driver virtio_gpu_driver;
|
||||
extern const struct pci_driver e1000_driver;
|
||||
|
||||
// Order matters here in one respect: virtio-gpu is offered the bus before the
|
||||
// VBE framebuffer driver, so that on a machine with both, the device with real
|
||||
// mode setting and an explicit flush wins the display class. vga_pci still
|
||||
// claims the stdvga card underneath, which is what the boot splash and the
|
||||
// text console are drawn on.
|
||||
static const struct pci_driver *drivers[] = {
|
||||
&virtio_gpu_driver,
|
||||
&vga_pci_driver,
|
||||
&e1000_driver,
|
||||
&xhci_driver,
|
||||
};
|
||||
|
||||
void
|
||||
pci_bind_all(void)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(drivers) / sizeof(drivers[0]); i++)
|
||||
pci_bind(drivers[i]);
|
||||
}
|
||||
|
||||
// USB class drivers, same arrangement and for the same reason. These are not
|
||||
// found on the PCI bus -- they are offered a device by the USB core once its
|
||||
// interface descriptor has been read -- but a driver you cannot find in a
|
||||
// list is a driver nobody knows is there, whichever bus it sits on.
|
||||
//
|
||||
// To add one: write kernel/drivers/usb_<name>.c exporting a struct
|
||||
// usb_driver, declare it here, add it to the array and to KERN_DRIVERS.
|
||||
|
||||
extern const struct usb_driver usb_hid_driver;
|
||||
extern const struct usb_driver usb_storage_driver;
|
||||
extern const struct usb_driver usb_hub_driver;
|
||||
|
||||
static const struct usb_driver *usb_drivers[] = {
|
||||
&usb_hid_driver,
|
||||
&usb_storage_driver,
|
||||
&usb_hub_driver,
|
||||
};
|
||||
|
||||
int usb_offer(const struct usb_driver *, struct usb_dev *);
|
||||
|
||||
void
|
||||
usb_bind_all(struct usb_dev *dev)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(usb_drivers) / sizeof(usb_drivers[0]); i++)
|
||||
if (usb_offer(usb_drivers[i], dev)) {
|
||||
// Same as the PCI path: publish it in the bus-agnostic list so that
|
||||
// "what is attached" has one answer covering every bus. Class comes from
|
||||
// the driver that claimed it rather than the USB class code, because a
|
||||
// driver knows what it made of the device and the descriptor only says
|
||||
// what the device claims to be.
|
||||
const char *n = usb_drivers[i]->name;
|
||||
struct device *d = device_add(n, DEVBUS_USB, usb_drivers[i]->devclass, n);
|
||||
if (d)
|
||||
device_set_desc(d, usb_drivers[i]->name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Devices that are not enumerated by any bus -- they are simply known to be
|
||||
// there on a PC, and the driver pokes fixed I/O ports to find out whether it
|
||||
// is right. Listing them matters because a device manager that shows the
|
||||
// graphics card but not the keyboard is not showing the machine.
|
||||
//
|
||||
// Registered here rather than inside each driver's init so that the whole set
|
||||
// is visible in one place, which is the same reasoning as the driver arrays
|
||||
// above.
|
||||
void
|
||||
platform_register_devices(void)
|
||||
{
|
||||
struct device *d;
|
||||
|
||||
if ((d = device_add("kbd", DEVBUS_PLATFORM, DEVCLASS_INPUT, "ps2kbd")))
|
||||
device_set_desc(d, "PS/2 keyboard");
|
||||
if ((d = device_add("mouse", DEVBUS_PLATFORM, DEVCLASS_INPUT, "ps2mouse")))
|
||||
device_set_desc(d, "PS/2 mouse");
|
||||
if ((d = device_add("uart", DEVBUS_PLATFORM, DEVCLASS_OTHER, "uart8250")))
|
||||
device_set_desc(d, "16550 serial port");
|
||||
if ((d = device_add("ide", DEVBUS_PLATFORM, DEVCLASS_STORAGE, "ide")))
|
||||
device_set_desc(d, "ATA disk controller");
|
||||
}
|
||||
209
kernel/drivers/usb/core/usb.c
Normal file
209
kernel/drivers/usb/core/usb.c
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
// The USB core: everything between "the controller can talk to a device" and
|
||||
// "a driver knows what the device is".
|
||||
//
|
||||
// The split matters. xhci.c deals in TRBs, slot IDs and doorbells; nothing
|
||||
// here knows any of those words. A class driver deals in reports and
|
||||
// commands; nothing here knows those either. What lives in the middle is the
|
||||
// part of USB that is the same for every device ever made: read the
|
||||
// configuration, choose one, look at the interface, find someone who wants it.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "spinlock.h"
|
||||
#include "dma.h"
|
||||
#include "usb.h"
|
||||
|
||||
// A staging buffer for control transfers.
|
||||
//
|
||||
// It exists because a descriptor has to be read into memory the controller
|
||||
// can reach and that does not straddle a 64 KB boundary -- a rule that holds
|
||||
// for xHCI data stages and that a static buffer inside some other structure
|
||||
// satisfies only by luck. One page, allocated once, copied out of afterwards.
|
||||
static void *scratch;
|
||||
|
||||
static void *
|
||||
get_scratch(void)
|
||||
{
|
||||
if(scratch == 0)
|
||||
scratch = dma_alloc(4096, 4096);
|
||||
return scratch;
|
||||
}
|
||||
|
||||
static int
|
||||
get_descriptor(struct usb_dev *dev, int type, int index, void *buf, int len)
|
||||
{
|
||||
struct usb_setup s;
|
||||
|
||||
// Clear first. The scratch buffer is shared by every device on the bus, and
|
||||
// a control transfer reports success without saying how many bytes it
|
||||
// actually moved -- so a device that answers short leaves the previous
|
||||
// device's descriptor visible past the end of its own, and the parser
|
||||
// happily walks into it. Zeroed bytes stop the walk instead, because a
|
||||
// descriptor whose length is 0 is rejected.
|
||||
memset(buf, 0, len);
|
||||
|
||||
s.bmRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
|
||||
s.bRequest = USB_REQ_GET_DESCRIPTOR;
|
||||
s.wValue = (type << 8) | index;
|
||||
s.wIndex = 0;
|
||||
s.wLength = len;
|
||||
return dev->ops->control(dev, &s, buf, len);
|
||||
}
|
||||
|
||||
static int
|
||||
set_configuration(struct usb_dev *dev, int value)
|
||||
{
|
||||
struct usb_setup s;
|
||||
|
||||
s.bmRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
|
||||
s.bRequest = USB_REQ_SET_CONFIG;
|
||||
s.wValue = value;
|
||||
s.wIndex = 0;
|
||||
s.wLength = 0;
|
||||
return dev->ops->control(dev, &s, 0, 0);
|
||||
}
|
||||
|
||||
// Walk the configuration descriptor.
|
||||
//
|
||||
// What comes back from GET_DESCRIPTOR(CONFIG) is not one descriptor: it is
|
||||
// the configuration descriptor followed by every interface and endpoint
|
||||
// descriptor belonging to it, packed end to end, each one starting with its
|
||||
// own length. That length is the only thing that can be relied on to find the
|
||||
// next -- descriptor types this kernel has never heard of appear in the
|
||||
// middle (HID's own, class-specific ones) and skipping them by size is the
|
||||
// only way through. A parser that assumed a fixed order would work on one
|
||||
// device and fail on the next.
|
||||
static void
|
||||
parse_config(struct usb_dev *dev)
|
||||
{
|
||||
int off = 0;
|
||||
int seen_iface = 0;
|
||||
|
||||
dev->has_intr_in = dev->has_bulk_in = dev->has_bulk_out = 0;
|
||||
|
||||
while(off + 2 <= dev->conflen){
|
||||
uint8 len = dev->conf[off];
|
||||
uint8 type = dev->conf[off + 1];
|
||||
|
||||
if(len < 2 || off + len > dev->conflen)
|
||||
break; // malformed: stop rather than guess
|
||||
|
||||
if(type == USB_DT_INTERFACE && len >= (int)sizeof(dev->iface)){
|
||||
if(seen_iface)
|
||||
break; // first interface only, for now
|
||||
memmove(&dev->iface, &dev->conf[off], sizeof(dev->iface));
|
||||
seen_iface = 1;
|
||||
} else if(type == USB_DT_ENDPOINT && seen_iface &&
|
||||
len >= (int)sizeof(struct usb_endpoint_desc)){
|
||||
struct usb_endpoint_desc ep;
|
||||
int in;
|
||||
|
||||
memmove(&ep, &dev->conf[off], sizeof(ep));
|
||||
in = USB_EP_IS_IN(ep.bEndpointAddress);
|
||||
|
||||
switch(USB_EP_XFERTYPE(ep.bmAttributes)){
|
||||
case USB_EP_INTERRUPT:
|
||||
if(in && !dev->has_intr_in){ dev->intr_in = ep; dev->has_intr_in = 1; }
|
||||
break;
|
||||
case USB_EP_BULK:
|
||||
if(in && !dev->has_bulk_in){ dev->bulk_in = ep; dev->has_bulk_in = 1; }
|
||||
if(!in && !dev->has_bulk_out){ dev->bulk_out = ep; dev->has_bulk_out = 1; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
off += len;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class drivers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
extern const struct usb_driver usb_hid_driver;
|
||||
void usb_bind_all(struct usb_dev *dev);
|
||||
|
||||
static int
|
||||
matches(const struct usb_driver *drv, struct usb_dev *dev)
|
||||
{
|
||||
if(drv->class != 0xFF && drv->class != dev->iface.bInterfaceClass)
|
||||
return 0;
|
||||
if(drv->subclass != 0xFF && drv->subclass != dev->iface.bInterfaceSubClass)
|
||||
return 0;
|
||||
if(drv->protocol != 0xFF && drv->protocol != dev->iface.bInterfaceProtocol)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Offer a device to one driver. Returns 1 if it was claimed.
|
||||
int
|
||||
usb_offer(const struct usb_driver *drv, struct usb_dev *dev)
|
||||
{
|
||||
if(dev->driver || !matches(drv, dev))
|
||||
return 0;
|
||||
if(drv->probe(dev) != 0)
|
||||
return 0;
|
||||
dev->driver = drv->name;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
usb_attach(struct usb_dev *dev)
|
||||
{
|
||||
struct usb_config_desc *cd;
|
||||
void *buf = get_scratch();
|
||||
int total;
|
||||
|
||||
if(buf == 0)
|
||||
return;
|
||||
|
||||
// Nine bytes first, because wTotalLength is in them and there is no other
|
||||
// way to know how much to ask for.
|
||||
if(get_descriptor(dev, USB_DT_CONFIG, 0, buf, 9) < 0){
|
||||
cprintf("usb: slot %d would not give up its configuration\n", dev->slot);
|
||||
return;
|
||||
}
|
||||
cd = buf;
|
||||
total = cd->wTotalLength;
|
||||
|
||||
// A configuration that does not contain its own descriptor is not one we
|
||||
// can do anything with, and reading it as a length would walk off the end.
|
||||
if(cd->bDescriptorType != USB_DT_CONFIG || total < (int)sizeof(*cd)){
|
||||
cprintf("usb: slot %d returned a malformed configuration descriptor\n",
|
||||
dev->slot);
|
||||
return;
|
||||
}
|
||||
if(total > USB_MAXCONF)
|
||||
total = USB_MAXCONF;
|
||||
|
||||
if(get_descriptor(dev, USB_DT_CONFIG, 0, buf, total) < 0)
|
||||
return;
|
||||
memmove(dev->conf, buf, total);
|
||||
dev->conflen = total;
|
||||
|
||||
parse_config(dev);
|
||||
|
||||
// A device is not obliged to do anything at all until it has been
|
||||
// configured -- before this, endpoints other than 0 do not exist.
|
||||
if(set_configuration(dev, cd->bConfigurationValue) < 0){
|
||||
cprintf("usb: slot %d refused SET_CONFIGURATION\n", dev->slot);
|
||||
return;
|
||||
}
|
||||
|
||||
cprintf("usb: interface class %d.%d protocol %d, %d endpoint%s%s%s%s\n",
|
||||
dev->iface.bInterfaceClass, dev->iface.bInterfaceSubClass,
|
||||
dev->iface.bInterfaceProtocol, dev->iface.bNumEndpoints,
|
||||
dev->iface.bNumEndpoints == 1 ? "" : "s",
|
||||
dev->has_intr_in ? ", interrupt IN" : "",
|
||||
dev->has_bulk_in ? ", bulk IN" : "",
|
||||
dev->has_bulk_out ? ", bulk OUT" : "");
|
||||
|
||||
usb_bind_all(dev);
|
||||
|
||||
if(dev->driver == 0)
|
||||
cprintf("usb: no driver for this device\n");
|
||||
}
|
||||
305
kernel/drivers/usb/hid/usb_hid.c
Normal file
305
kernel/drivers/usb/hid/usb_hid.c
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
// USB HID, boot protocol: keyboards and mice.
|
||||
//
|
||||
// Boot protocol is the reason a USB keyboard works in a BIOS setup screen
|
||||
// before any operating system exists. A HID device normally describes its
|
||||
// reports with a report descriptor -- a small stack language that has to be
|
||||
// parsed to know which bit means which key -- but a device claiming the boot
|
||||
// subclass also promises to support one fixed layout on request. Eight bytes
|
||||
// for a keyboard, three for a mouse, defined in the HID specification and
|
||||
// identical on every device.
|
||||
//
|
||||
// That is worth taking. A full report-descriptor parser is a real piece of
|
||||
// work and buys nothing for a keyboard; it is what a game controller or a
|
||||
// touchscreen will need, and it can arrive with them.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "dma.h"
|
||||
#include "usb.h"
|
||||
#include "input.h"
|
||||
|
||||
#define NHID 4
|
||||
|
||||
struct hid {
|
||||
int in_use;
|
||||
int proto; // USB_HID_PROTO_KEYBOARD or _MOUSE
|
||||
uchar prev[8]; // the previous report, for edge detection
|
||||
};
|
||||
|
||||
static struct hid hids[NHID];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Keyboard
|
||||
//
|
||||
// The report is: modifiers, a reserved byte, then up to six usage IDs of keys
|
||||
// currently held. It is a *state*, not an event -- a key that stays down
|
||||
// appears in every report until it is released, so a character is emitted
|
||||
// only for usages that were not in the previous report. Sending one per
|
||||
// report instead would repeat every held key a few hundred times a second.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define HID_MOD_CTRL 0x11 // left | right
|
||||
#define HID_MOD_SHIFT 0x22
|
||||
#define HID_MOD_ALT 0x44
|
||||
|
||||
// Usage IDs 0x00-0x38 of the Keyboard/Keypad page. 0 means "nothing to type".
|
||||
static const uchar hid_unshifted[0x39] = {
|
||||
0, 0, 0, 0,
|
||||
'a','b','c','d','e','f','g','h','i','j','k','l','m',
|
||||
'n','o','p','q','r','s','t','u','v','w','x','y','z',
|
||||
'1','2','3','4','5','6','7','8','9','0',
|
||||
'\n', 0x1B, '\b', '\t', ' ',
|
||||
'-','=','[',']','\\','#',';','\'','`',',','.','/',
|
||||
};
|
||||
|
||||
static const uchar hid_shifted[0x39] = {
|
||||
0, 0, 0, 0,
|
||||
'A','B','C','D','E','F','G','H','I','J','K','L','M',
|
||||
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
|
||||
'!','@','#','$','%','^','&','*','(',')',
|
||||
'\n', 0x1B, '\b', '\t', ' ',
|
||||
'_','+','{','}','|','~',':','"','~','<','>','?',
|
||||
};
|
||||
|
||||
// Characters decoded from a report, waiting to be handed to the console.
|
||||
//
|
||||
// The console pulls input through a getc function rather than being pushed
|
||||
// to (consoleintr takes one), which is exactly the shape the PS/2 driver
|
||||
// needs and exactly the wrong shape here -- a USB report arrives complete,
|
||||
// with up to six keys in it. So the report is decoded into this queue and
|
||||
// the console then drains it through the same path as any other keyboard,
|
||||
// which means the entire line discipline is reused rather than reimplemented.
|
||||
#define KQSIZE 64
|
||||
static struct {
|
||||
uchar buf[KQSIZE];
|
||||
uint r, w;
|
||||
} kq;
|
||||
|
||||
static void
|
||||
kq_put(int c)
|
||||
{
|
||||
if(kq.w - kq.r < KQSIZE)
|
||||
kq.buf[kq.w++ % KQSIZE] = c;
|
||||
}
|
||||
|
||||
static int
|
||||
usb_kbd_getc(void)
|
||||
{
|
||||
if(kq.r == kq.w)
|
||||
return -1;
|
||||
return kq.buf[kq.r++ % KQSIZE];
|
||||
}
|
||||
|
||||
// Was this usage in that report?
|
||||
static int
|
||||
was_down(const uchar *prev, uchar usage)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 2; i < 8; i++)
|
||||
if(prev[i] == usage)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Turn the difference between two reports into press and release events for
|
||||
// /dev/input/event0.
|
||||
//
|
||||
// The console path below cannot do this job: it emits a character per new key
|
||||
// and nothing at all for a release, because a character stream has no way to
|
||||
// say "released". A program that wants to know a key is being *held* -- which
|
||||
// is every game and every compositor -- needs exactly the events this makes.
|
||||
static void
|
||||
keyboard_events(const uchar *prev, const uchar *rep)
|
||||
{
|
||||
int i, bit, n = 0;
|
||||
|
||||
for(bit = 0; bit < 8; bit++){
|
||||
int now = (rep[0] >> bit) & 1;
|
||||
int then = (prev[0] >> bit) & 1;
|
||||
if(now != then){
|
||||
input_report(INPUT_KBD, EV_KEY, input_hid_keycode(0xE0 + bit), now);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 2; i < 8; i++){
|
||||
uchar u = rep[i];
|
||||
if(u && !was_down(prev, u)){
|
||||
int k = input_hid_keycode(u);
|
||||
if(k){ input_report(INPUT_KBD, EV_KEY, k, 1); n++; }
|
||||
}
|
||||
}
|
||||
for(i = 2; i < 8; i++){
|
||||
uchar u = prev[i];
|
||||
if(u && !was_down(rep, u)){
|
||||
int k = input_hid_keycode(u);
|
||||
if(k){ input_report(INPUT_KBD, EV_KEY, k, 0); n++; }
|
||||
}
|
||||
}
|
||||
|
||||
if(n)
|
||||
input_sync(INPUT_KBD);
|
||||
}
|
||||
|
||||
static void
|
||||
keyboard_report(struct hid *h, const uchar *rep, int len)
|
||||
{
|
||||
uchar mods = rep[0];
|
||||
int i, c;
|
||||
|
||||
if(len < 8)
|
||||
return;
|
||||
|
||||
keyboard_events(h->prev, rep);
|
||||
|
||||
// Grabbed: the events above still went to /dev/input/event0, but nothing
|
||||
// reaches the console. Note prev[] is updated before returning, or the next
|
||||
// report would look like every held key had just been pressed again.
|
||||
if(input_is_grabbed(INPUT_KBD)){
|
||||
memmove(h->prev, rep, 8);
|
||||
return;
|
||||
}
|
||||
|
||||
for(i = 2; i < 8; i++){
|
||||
uchar usage = rep[i];
|
||||
|
||||
if(usage == 0 || usage >= 0x39)
|
||||
continue;
|
||||
if(was_down(h->prev, usage))
|
||||
continue; // still held from last time, not a new press
|
||||
|
||||
c = (mods & HID_MOD_SHIFT) ? hid_shifted[usage] : hid_unshifted[usage];
|
||||
if(c == 0)
|
||||
continue;
|
||||
|
||||
// Ctrl folds a letter down into its control character, which is how ^C
|
||||
// and ^D reach the line discipline at all.
|
||||
if(mods & HID_MOD_CTRL){
|
||||
if(c >= 'a' && c <= 'z')
|
||||
c = c - 'a' + 1;
|
||||
else if(c >= 'A' && c <= 'Z')
|
||||
c = c - 'A' + 1;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
kq_put(c);
|
||||
}
|
||||
|
||||
memmove(h->prev, rep, 8);
|
||||
consoleintr(usb_kbd_getc);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mouse
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
mouse_report(struct hid *h, const uchar *rep, int len)
|
||||
{
|
||||
int dx, dy;
|
||||
|
||||
(void)h;
|
||||
if(len < 3)
|
||||
return;
|
||||
|
||||
// The deltas are signed bytes. Reading them as unsigned is the classic way
|
||||
// to get a pointer that only travels right and down.
|
||||
dx = (signed char)rep[1];
|
||||
dy = (signed char)rep[2];
|
||||
|
||||
// HID's Y axis points down, and so does the screen's. The PS/2 protocol's
|
||||
// points up, which is why mouseintr subtracts where this adds.
|
||||
mouse_inject(dx, dy, rep[0] & 0x07);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
hid_complete(struct usb_dev *dev, void *buf, int len)
|
||||
{
|
||||
struct hid *h = dev->drvdata;
|
||||
|
||||
if(h == 0)
|
||||
return;
|
||||
if(h->proto == USB_HID_PROTO_KEYBOARD)
|
||||
keyboard_report(h, buf, len);
|
||||
else
|
||||
mouse_report(h, buf, len);
|
||||
}
|
||||
|
||||
static int
|
||||
hid_request(struct usb_dev *dev, int request, int value)
|
||||
{
|
||||
struct usb_setup s;
|
||||
|
||||
s.bmRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
|
||||
s.bRequest = request;
|
||||
s.wValue = value;
|
||||
s.wIndex = dev->iface.bInterfaceNumber;
|
||||
s.wLength = 0;
|
||||
return dev->ops->control(dev, &s, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
hid_probe(struct usb_dev *dev)
|
||||
{
|
||||
struct hid *h = 0;
|
||||
int i, proto = dev->iface.bInterfaceProtocol;
|
||||
|
||||
if(!dev->has_intr_in){
|
||||
cprintf("usb-hid: no interrupt IN endpoint; nothing to read\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for(i = 0; i < NHID; i++)
|
||||
if(!hids[i].in_use){ h = &hids[i]; break; }
|
||||
if(h == 0)
|
||||
return -1;
|
||||
|
||||
// Ask for the fixed layout explicitly. A device that supports the boot
|
||||
// subclass still starts in report protocol, so without this the reports
|
||||
// arrive in whatever shape its report descriptor declares -- which we have
|
||||
// not read and cannot interpret.
|
||||
if(hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){
|
||||
cprintf("usb-hid: SET_PROTOCOL(boot) refused\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Idle rate 0 = "report only when something changes". The default is to
|
||||
// resend the current state every few milliseconds forever, which for a
|
||||
// keyboard means a constant stream of interrupts saying nothing.
|
||||
hid_request(dev, USB_HID_SET_IDLE, 0);
|
||||
|
||||
if(dev->ops->open_ep(dev, &dev->intr_in) < 0)
|
||||
return -1;
|
||||
|
||||
h->in_use = 1;
|
||||
h->proto = proto;
|
||||
dev->drvdata = h;
|
||||
dev->complete = hid_complete;
|
||||
|
||||
// Arm the first transfer. Until a TRB is waiting on the endpoint the device
|
||||
// has nowhere to put a report, so nothing would ever arrive.
|
||||
if(dev->ops->queue_in(dev, dev->intr_in.bEndpointAddress, 0, 0) < 0)
|
||||
return -1;
|
||||
|
||||
cprintf("usb-hid: %s on slot %d, endpoint 0x%x every %d ms\n",
|
||||
proto == USB_HID_PROTO_KEYBOARD ? "keyboard" :
|
||||
proto == USB_HID_PROTO_MOUSE ? "mouse" : "device",
|
||||
dev->slot, dev->intr_in.bEndpointAddress, dev->intr_in.bInterval);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Boot subclass only. A HID device that does not claim it needs its report
|
||||
// descriptor parsed, and we would have nothing useful to do with it.
|
||||
const struct usb_driver usb_hid_driver = {
|
||||
.name = "usb-hid",
|
||||
.class = USB_CLASS_HID,
|
||||
.subclass = USB_HID_SUBCLASS_BOOT,
|
||||
.protocol = 0xFF,
|
||||
.devclass = DEVCLASS_INPUT,
|
||||
.probe = hid_probe,
|
||||
};
|
||||
185
kernel/drivers/usb/hub/usb_hub.c
Normal file
185
kernel/drivers/usb/hub/usb_hub.c
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
// USB hub class driver.
|
||||
//
|
||||
// A hub is what makes USB a tree rather than a handful of sockets, and
|
||||
// nothing about driving one is special: every operation is an ordinary
|
||||
// control request sent to the hub as a device. "Reset port 3" is a
|
||||
// SET_FEATURE(PORT_RESET, wIndex=3). The hub has an interrupt endpoint too,
|
||||
// which reports which ports have changed, but that is only needed for
|
||||
// hotplug -- at boot it is enough to walk every port and look.
|
||||
//
|
||||
// What *is* special is what the host controller has to be told. xHCI does not
|
||||
// discover topology; it is informed of it. A device three hubs deep is
|
||||
// addressed by a route string naming the port taken at each tier, and a
|
||||
// slow device behind a fast hub is scheduled against that hub's transaction
|
||||
// translator rather than against the device. So this driver's real job is
|
||||
// less "operate the hub" than "tell the controller what shape the tree is".
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "dma.h"
|
||||
#include "usb.h"
|
||||
|
||||
#define MAX_PORTS 15 // per hub; more than any real bus hub has
|
||||
|
||||
static int
|
||||
hub_request(struct usb_dev *dev, int type, int req, int value, int index,
|
||||
void *buf, int len)
|
||||
{
|
||||
struct usb_setup s;
|
||||
|
||||
s.bmRequestType = type;
|
||||
s.bRequest = req;
|
||||
s.wValue = value;
|
||||
s.wIndex = index;
|
||||
s.wLength = len;
|
||||
return dev->ops->control(dev, &s, buf, len);
|
||||
}
|
||||
|
||||
static int
|
||||
port_set_feature(struct usb_dev *dev, int port, int feature)
|
||||
{
|
||||
return hub_request(dev, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER,
|
||||
USB_REQ_SET_FEATURE, feature, port, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
port_clear_feature(struct usb_dev *dev, int port, int feature)
|
||||
{
|
||||
return hub_request(dev, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER,
|
||||
USB_REQ_CLEAR_FEATURE, feature, port, 0, 0);
|
||||
}
|
||||
|
||||
// Returns wPortStatus, or -1. The change bits are in the upper half and are
|
||||
// deliberately dropped here -- they are acknowledged separately.
|
||||
static int
|
||||
port_status(struct usb_dev *dev, int port, void *scratch)
|
||||
{
|
||||
uint16 *st = scratch;
|
||||
|
||||
if(hub_request(dev, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_OTHER,
|
||||
USB_REQ_GET_STATUS, 0, port, scratch, 4) < 0)
|
||||
return -1;
|
||||
return st[0];
|
||||
}
|
||||
|
||||
// Bring up whatever is plugged into one downstream port.
|
||||
static void
|
||||
hub_port(struct usb_dev *hub, int port, void *scratch)
|
||||
{
|
||||
int st, speed, i;
|
||||
|
||||
st = port_status(hub, port, scratch);
|
||||
if(st < 0 || !(st & PORTSTAT_CONNECTION))
|
||||
return;
|
||||
|
||||
// Acknowledge the connect-change bit before resetting. Leaving it set is
|
||||
// harmless here but confuses any later attempt to use these bits for
|
||||
// hotplug, which is what they are actually for.
|
||||
port_clear_feature(hub, port, PORT_FEAT_C_CONNECTION);
|
||||
|
||||
if(port_set_feature(hub, port, PORT_FEAT_RESET) < 0){
|
||||
cprintf("usb-hub: port %d would not reset\n", port);
|
||||
return;
|
||||
}
|
||||
|
||||
// A hub does not report reset completion by clearing the request; it sets
|
||||
// C_PORT_RESET and enables the port. Poll for the enable rather than
|
||||
// guessing at a delay, because the time depends on the device.
|
||||
for(i = 0; i < 200; i++){
|
||||
microdelay(1000);
|
||||
st = port_status(hub, port, scratch);
|
||||
if(st >= 0 && !(st & PORTSTAT_RESET) && (st & PORTSTAT_ENABLE))
|
||||
break;
|
||||
}
|
||||
port_clear_feature(hub, port, PORT_FEAT_C_RESET);
|
||||
|
||||
st = port_status(hub, port, scratch);
|
||||
if(st < 0 || !(st & PORTSTAT_ENABLE)){
|
||||
cprintf("usb-hub: port %d did not enable after reset\n", port);
|
||||
return;
|
||||
}
|
||||
|
||||
// Speed comes out of two status bits rather than a field, and the absence
|
||||
// of both means full speed.
|
||||
if(st & PORTSTAT_LOWSPEED)
|
||||
speed = USB_SPEED_LOW;
|
||||
else if(st & PORTSTAT_HIGHSPEED)
|
||||
speed = USB_SPEED_HIGH;
|
||||
else
|
||||
speed = USB_SPEED_FULL;
|
||||
|
||||
// A device needs time to settle after reset before it will answer on
|
||||
// endpoint 0. The specification calls this TRSTRCY and allows 10 ms.
|
||||
microdelay(20000);
|
||||
|
||||
hub->ops->attach(hub, port, speed);
|
||||
}
|
||||
|
||||
static int
|
||||
hub_probe(struct usb_dev *dev)
|
||||
{
|
||||
struct usb_hub_desc *hd;
|
||||
void *scratch;
|
||||
int nports, mtt, i;
|
||||
|
||||
scratch = dma_alloc(64, 64);
|
||||
if(scratch == 0)
|
||||
return -1;
|
||||
|
||||
// The hub descriptor is class-specific, so it comes from a class GET
|
||||
// request rather than the standard one -- same request number, different
|
||||
// recipient and type.
|
||||
if(hub_request(dev, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE,
|
||||
USB_REQ_GET_DESCRIPTOR, USB_DT_HUB << 8, 0,
|
||||
scratch, sizeof(struct usb_hub_desc)) < 0){
|
||||
cprintf("usb-hub: no hub descriptor\n");
|
||||
dma_free(scratch, 64);
|
||||
return -1;
|
||||
}
|
||||
|
||||
hd = scratch;
|
||||
nports = hd->bNbrPorts;
|
||||
if(nports < 1 || nports > MAX_PORTS){
|
||||
cprintf("usb-hub: %d ports is not a number of ports\n", nports);
|
||||
dma_free(scratch, 64);
|
||||
return -1;
|
||||
}
|
||||
mtt = (hd->wHubCharacteristics & HUB_CHAR_TT_MASK) != 0;
|
||||
|
||||
cprintf("usb-hub: %d ports on slot %d\n", nports, dev->slot);
|
||||
|
||||
// Tell the controller *before* powering anything on. Addressing a device
|
||||
// behind a slot the controller does not know is a hub fails, and it fails
|
||||
// at Address Device with a completion code rather than anywhere useful.
|
||||
if(dev->ops->set_hub(dev, nports, mtt) < 0){
|
||||
dma_free(scratch, 64);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Ports come up unpowered. bPwrOn2PwrGood is how long the hub says it needs
|
||||
// after switching power on before a device behind it is worth talking to,
|
||||
// in 2 ms units -- a number the hub knows and we do not.
|
||||
for(i = 1; i <= nports; i++)
|
||||
port_set_feature(dev, i, PORT_FEAT_POWER);
|
||||
microdelay((hd->bPwrOn2PwrGood * 2 + 10) * 1000);
|
||||
|
||||
for(i = 1; i <= nports; i++)
|
||||
hub_port(dev, i, scratch);
|
||||
|
||||
dma_free(scratch, 64);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Any hub, whatever its protocol: 0 is a full-speed hub, 1 single-TT, 2
|
||||
// multi-TT, and they differ only in how transactions are translated, which
|
||||
// is the controller's problem rather than ours.
|
||||
const struct usb_driver usb_hub_driver = {
|
||||
.name = "usb-hub",
|
||||
.class = USB_CLASS_HUB,
|
||||
.subclass = 0xFF,
|
||||
.protocol = 0xFF,
|
||||
.devclass = DEVCLASS_BUS,
|
||||
.probe = hub_probe,
|
||||
};
|
||||
395
kernel/drivers/usb/storage/usb_storage.c
Normal file
395
kernel/drivers/usb/storage/usb_storage.c
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
// USB mass storage: bulk-only transport, SCSI transparent command set.
|
||||
//
|
||||
// Almost every USB stick, external drive and card reader is this, and the
|
||||
// name is a fair description of the whole protocol: there are exactly two
|
||||
// bulk endpoints and everything happens by sending three things over them.
|
||||
//
|
||||
// command a 31-byte wrapper (CBW) carrying a SCSI command block, OUT
|
||||
// data the payload, IN or OUT, if the command has one
|
||||
// status a 13-byte wrapper (CSW) saying whether it worked, IN
|
||||
//
|
||||
// That is the entire transport. The interesting content is SCSI, which is
|
||||
// what the wrapper carries and what the device actually speaks -- so a USB
|
||||
// stick is a SCSI disk that happens to be reached over USB, and the commands
|
||||
// below (INQUIRY, READ CAPACITY, READ(10), WRITE(10)) are the same ones a
|
||||
// SCSI drive answered in 1990.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "buf.h"
|
||||
#include "dma.h"
|
||||
#include "usb.h"
|
||||
|
||||
#define CBW_SIGNATURE 0x43425355 // "USBC"
|
||||
#define CSW_SIGNATURE 0x53425355 // "USBS"
|
||||
|
||||
struct cbw {
|
||||
uint32 signature;
|
||||
uint32 tag;
|
||||
uint32 data_len;
|
||||
uint8 flags; // 0x80 = device-to-host
|
||||
uint8 lun;
|
||||
uint8 cb_len; // 1..16
|
||||
uint8 cb[16];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct csw {
|
||||
uint32 signature;
|
||||
uint32 tag;
|
||||
uint32 residue;
|
||||
uint8 status; // 0 = passed, 1 = failed, 2 = phase error
|
||||
} __attribute__((packed));
|
||||
|
||||
// SCSI opcodes
|
||||
#define SCSI_TEST_UNIT_READY 0x00
|
||||
#define SCSI_REQUEST_SENSE 0x03
|
||||
#define SCSI_INQUIRY 0x12
|
||||
#define SCSI_READ_CAPACITY10 0x25
|
||||
#define SCSI_READ10 0x28
|
||||
#define SCSI_WRITE10 0x2A
|
||||
|
||||
#define SECTOR_SIZE 512
|
||||
|
||||
static struct {
|
||||
struct usb_dev *dev;
|
||||
int present;
|
||||
uint32 nsectors;
|
||||
uint32 sector_size;
|
||||
uint32 tag;
|
||||
void *xfer; // DMA bounce buffer, one block
|
||||
struct cbw *cbw; // allocated once, not per command
|
||||
struct csw *csw;
|
||||
struct sleeplock lock; // one command at a time
|
||||
} ums;
|
||||
|
||||
// SCSI is big-endian, everywhere, for everything. x86 is not.
|
||||
static void
|
||||
be32(uint8 *p, uint32 v)
|
||||
{
|
||||
p[0] = v >> 24; p[1] = v >> 16; p[2] = v >> 8; p[3] = v;
|
||||
}
|
||||
|
||||
static uint32
|
||||
rd_be32(const uint8 *p)
|
||||
{
|
||||
return ((uint32)p[0] << 24) | ((uint32)p[1] << 16) |
|
||||
((uint32)p[2] << 8) | p[3];
|
||||
}
|
||||
|
||||
// One complete command-data-status exchange.
|
||||
//
|
||||
// `buf`, when there is data, must be DMA memory -- it is handed to the
|
||||
// controller as a physical address.
|
||||
static int
|
||||
bot_command(const uint8 *cdb, int cdb_len, void *buf, int len, int in)
|
||||
{
|
||||
struct usb_dev *dev = ums.dev;
|
||||
struct cbw *cbw = ums.cbw;
|
||||
struct csw *csw = ums.csw;
|
||||
uint32 tag = ++ums.tag;
|
||||
int i, data_ok = 1;
|
||||
|
||||
memset(cbw, 0, sizeof(*cbw));
|
||||
cbw->signature = CBW_SIGNATURE;
|
||||
cbw->tag = tag;
|
||||
cbw->data_len = len;
|
||||
cbw->flags = in ? 0x80 : 0x00;
|
||||
cbw->lun = 0;
|
||||
cbw->cb_len = cdb_len;
|
||||
for(i = 0; i < cdb_len && i < 16; i++)
|
||||
cbw->cb[i] = cdb[i];
|
||||
|
||||
if(dev->ops->bulk(dev, dev->bulk_out.bEndpointAddress, cbw,
|
||||
sizeof(*cbw)) != (int)sizeof(*cbw)){
|
||||
cprintf("usb-storage: the device would not take the command\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(len > 0){
|
||||
int addr = in ? dev->bulk_in.bEndpointAddress
|
||||
: dev->bulk_out.bEndpointAddress;
|
||||
if(dev->ops->bulk(dev, addr, buf, len) < 0)
|
||||
data_ok = 0;
|
||||
}
|
||||
|
||||
// The status wrapper is not optional and not advisory: a device that has
|
||||
// sent its data is still waiting to be asked how it went.
|
||||
//
|
||||
// It is read even when the data phase failed, which is the whole point --
|
||||
// returning early there would leave the device still holding a status it
|
||||
// has not been asked for, so the *next* command's data phase would receive
|
||||
// this command's status instead. One failed read would turn into every
|
||||
// subsequent read failing, which is a far worse fault than the one that
|
||||
// started it, and one that looks nothing like its cause.
|
||||
memset(csw, 0, sizeof(*csw));
|
||||
if(dev->ops->bulk(dev, dev->bulk_in.bEndpointAddress, csw,
|
||||
sizeof(*csw)) != (int)sizeof(*csw)){
|
||||
cprintf("usb-storage: no status returned for command 0x%x\n", cdb[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(csw->signature != CSW_SIGNATURE || csw->tag != tag){
|
||||
cprintf("usb-storage: status wrapper does not match the command\n");
|
||||
return -1;
|
||||
}
|
||||
if(csw->status != 0){
|
||||
cprintf("usb-storage: command 0x%x failed (status %d)\n",
|
||||
cdb[0], csw->status);
|
||||
return -1;
|
||||
}
|
||||
if(!data_ok)
|
||||
return -1;
|
||||
|
||||
// Residue is what was *not* transferred, and a device is allowed to report
|
||||
// more than was asked for only if it is broken -- clamp rather than return
|
||||
// a negative length that a caller would read as an error code.
|
||||
if(csw->residue > (uint32)len)
|
||||
return -1;
|
||||
return len - (int)csw->residue;
|
||||
}
|
||||
|
||||
static int
|
||||
scsi_inquiry(char *vendor, char *product)
|
||||
{
|
||||
uint8 cdb[6] = { SCSI_INQUIRY, 0, 0, 0, 36, 0 };
|
||||
uint8 *r = ums.xfer;
|
||||
int i;
|
||||
|
||||
if(bot_command(cdb, sizeof(cdb), r, 36, 1) < 0)
|
||||
return -1;
|
||||
|
||||
// Bytes 8-15 are the vendor, 16-31 the product, space padded and not
|
||||
// NUL-terminated -- the only fixed-width strings left in computing.
|
||||
for(i = 0; i < 8; i++)
|
||||
vendor[i] = r[8 + i];
|
||||
vendor[8] = 0;
|
||||
for(i = 0; i < 16; i++)
|
||||
product[i] = r[16 + i];
|
||||
product[16] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
scsi_read_capacity(void)
|
||||
{
|
||||
uint8 cdb[10] = { SCSI_READ_CAPACITY10, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
uint8 *r = ums.xfer;
|
||||
|
||||
if(bot_command(cdb, sizeof(cdb), r, 8, 1) < 0)
|
||||
return -1;
|
||||
|
||||
// The first word is the address of the *last* sector, not the count.
|
||||
ums.nsectors = rd_be32(r) + 1;
|
||||
ums.sector_size = rd_be32(r + 4);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
scsi_rw(uint32 lba, int count, void *buf, int write)
|
||||
{
|
||||
uint8 cdb[10];
|
||||
|
||||
memset(cdb, 0, sizeof(cdb));
|
||||
cdb[0] = write ? SCSI_WRITE10 : SCSI_READ10;
|
||||
be32(&cdb[2], lba);
|
||||
cdb[7] = (count >> 8) & 0xFF;
|
||||
cdb[8] = count & 0xFF;
|
||||
|
||||
return bot_command(cdb, sizeof(cdb), buf, count * SECTOR_SIZE, !write);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Block device
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int
|
||||
usb_storage_present(void)
|
||||
{
|
||||
return ums.present;
|
||||
}
|
||||
|
||||
// Sync one buffer-cache block with the stick. Same contract as iderw() and
|
||||
// ramdiskrw(): on a read fill b->data and set B_VALID, on a write push it out
|
||||
// and clear B_DIRTY.
|
||||
void
|
||||
usb_storage_rw(struct buf *b)
|
||||
{
|
||||
int per_block = BSIZE / SECTOR_SIZE;
|
||||
uint32 lba = b->blockno * per_block;
|
||||
int write = (b->flags & B_DIRTY) != 0;
|
||||
int i, n = -1;
|
||||
|
||||
if(!holdingsleep(&b->lock))
|
||||
panic("usb_storage_rw: buf not locked");
|
||||
if(!ums.present)
|
||||
panic("usb_storage_rw: no USB disk");
|
||||
if(lba + per_block > ums.nsectors)
|
||||
panic("usb_storage_rw: block out of range");
|
||||
|
||||
// One command at a time. Bulk-only transport has no tagging beyond the CBW
|
||||
// tag and no queueing: a second command sent while the first is mid-phase
|
||||
// desynchronises the endpoint and every command after it fails.
|
||||
acquiresleep(&ums.lock);
|
||||
|
||||
// Removable media fails transiently in ways a fixed disk does not, so a
|
||||
// couple of retries are worth having before giving up.
|
||||
for(i = 0; i < 3; i++){
|
||||
if(write)
|
||||
memmove(ums.xfer, b->data, BSIZE);
|
||||
n = scsi_rw(lba, per_block, ums.xfer, write);
|
||||
if(n == BSIZE)
|
||||
break;
|
||||
cprintf("usb-storage: %s of block %d failed, retrying\n",
|
||||
write ? "write" : "read", b->blockno);
|
||||
}
|
||||
|
||||
releasesleep(&ums.lock);
|
||||
|
||||
// A read that failed must not return quietly. bread() hands the buffer
|
||||
// straight back to its caller, which has no way to ask whether it worked --
|
||||
// so leaving B_VALID clear would deliver whatever the buffer happened to
|
||||
// contain as though it were the block on disk. Corrupting a filesystem
|
||||
// silently is worse than stopping.
|
||||
if(n != BSIZE)
|
||||
panic("usb_storage_rw: transfer failed");
|
||||
|
||||
if(!write)
|
||||
memmove(b->data, ums.xfer, BSIZE);
|
||||
b->flags |= B_VALID;
|
||||
b->flags &= ~B_DIRTY;
|
||||
}
|
||||
|
||||
// Read block 1 through the ordinary buffer cache and report what is there.
|
||||
//
|
||||
// This is the only test that proves the whole path rather than a piece of it:
|
||||
// bread() -> bdevrw() -> usb_storage_rw() -> bulk-only transport -> SCSI
|
||||
// READ(10) -> an interrupt -> a sleeping process woken with data. Everything
|
||||
// before it -- INQUIRY answering, a plausible capacity -- can be true of a
|
||||
// device that cannot actually read a sector.
|
||||
//
|
||||
// Reached with `echo s > /proc/sysrq`.
|
||||
void
|
||||
usb_storage_dump(void)
|
||||
{
|
||||
struct buf *b;
|
||||
struct superblock *sb;
|
||||
|
||||
if(!ums.present){
|
||||
cprintf("usb-storage: no disk\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cprintf("usb-storage: %d sectors of %d bytes\n", ums.nsectors,
|
||||
ums.sector_size);
|
||||
|
||||
b = bread(USBDISK_DEV, 1);
|
||||
sb = (struct superblock*)b->data;
|
||||
cprintf("usb-storage: block 1 magic %x size %d nblocks %d ninodes %d\n",
|
||||
sb->magic, sb->size, sb->nblocks, sb->ninodes);
|
||||
brelse(b);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int
|
||||
storage_probe(struct usb_dev *dev)
|
||||
{
|
||||
char vendor[9], product[17];
|
||||
int i;
|
||||
|
||||
if(ums.present)
|
||||
return -1; // one disk is enough for now
|
||||
if(!dev->has_bulk_in || !dev->has_bulk_out){
|
||||
cprintf("usb-storage: needs a bulk endpoint in each direction\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
initsleeplock(&ums.lock, "usbms");
|
||||
ums.dev = dev;
|
||||
|
||||
// One block, used for every transfer. bread() hands us b->data, which lives
|
||||
// in the buffer cache rather than in DMA memory, so everything bounces
|
||||
// through here. The wrappers are allocated once too: they are used on every
|
||||
// single I/O, and churning the DMA extent list twice per block read would
|
||||
// fragment an arena that has no business fragmenting.
|
||||
ums.xfer = dma_alloc(BSIZE, 4096);
|
||||
ums.cbw = dma_alloc(sizeof(struct cbw), 64);
|
||||
ums.csw = dma_alloc(sizeof(struct csw), 64);
|
||||
if(ums.xfer == 0 || ums.cbw == 0 || ums.csw == 0)
|
||||
goto fail;
|
||||
|
||||
// Deliberately OUT first, which is the order that used to break.
|
||||
//
|
||||
// The slot context's Context Entries field must cover the highest DCI in
|
||||
// use, and xhci_open_ep originally wrote whichever endpoint it was adding.
|
||||
// Bulk OUT here has the higher DCI, so opening it first and then IN would
|
||||
// *lower* the field and silently invalidate the endpoint that was already
|
||||
// configured. Opening IN first hid that. Now that xhci_open_ep tracks the
|
||||
// maximum itself the order genuinely does not matter -- and doing it in the
|
||||
// formerly-broken order is what keeps that true.
|
||||
if(dev->ops->open_ep(dev, &dev->bulk_out) < 0 ||
|
||||
dev->ops->open_ep(dev, &dev->bulk_in) < 0)
|
||||
goto fail;
|
||||
|
||||
// Deliberately no GET MAX LUN. It is answered with a STALL by
|
||||
// single-LUN devices, and recovering from a STALL on endpoint 0 needs a
|
||||
// Reset Endpoint command this driver does not implement yet. LUN 0 is the
|
||||
// only one used below, so asking would gain nothing and could wedge us.
|
||||
|
||||
// A device that has just been configured is usually "not ready" for the
|
||||
// first command or two while it spins up or reads its own media.
|
||||
for(i = 0; i < 5; i++){
|
||||
uint8 cdb[6] = { SCSI_TEST_UNIT_READY, 0, 0, 0, 0, 0 };
|
||||
if(bot_command(cdb, sizeof(cdb), 0, 0, 0) >= 0)
|
||||
break;
|
||||
microdelay(100000);
|
||||
}
|
||||
|
||||
if(scsi_inquiry(vendor, product) < 0)
|
||||
goto fail;
|
||||
if(scsi_read_capacity() < 0)
|
||||
goto fail;
|
||||
|
||||
if(ums.sector_size != SECTOR_SIZE){
|
||||
cprintf("usb-storage: %d-byte sectors are not supported\n",
|
||||
ums.sector_size);
|
||||
goto fail;
|
||||
}
|
||||
if(ums.nsectors == 0){
|
||||
cprintf("usb-storage: device reports no capacity\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ums.present = 1;
|
||||
cprintf("usb-storage: %s %s, %d sectors (%d MB)\n",
|
||||
vendor, product, ums.nsectors,
|
||||
(int)((uint64)ums.nsectors * SECTOR_SIZE / (1024 * 1024)));
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
// Give the memory back and forget the device. Leaving ums.dev set would let
|
||||
// a later block read reach a device we decided we could not drive.
|
||||
if(ums.xfer) dma_free(ums.xfer, BSIZE);
|
||||
if(ums.cbw) dma_free(ums.cbw, sizeof(struct cbw));
|
||||
if(ums.csw) dma_free(ums.csw, sizeof(struct csw));
|
||||
ums.xfer = 0;
|
||||
ums.cbw = 0;
|
||||
ums.csw = 0;
|
||||
ums.dev = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const struct usb_driver usb_storage_driver = {
|
||||
.name = "usb-storage",
|
||||
.class = USB_CLASS_MASS_STORAGE,
|
||||
.subclass = USB_MSC_SUBCLASS_SCSI,
|
||||
.protocol = USB_MSC_PROTO_BOT,
|
||||
.devclass = DEVCLASS_STORAGE,
|
||||
.probe = storage_probe,
|
||||
};
|
||||
Loading…
Reference in a new issue