diff options
author | Rusty Russell <rusty@rustcorp.com.au> | 2007-07-26 10:41:03 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-07-26 11:35:17 -0700 |
commit | e2c9784325490c878b7f69aeec1bed98b288bd97 (patch) | |
tree | d474007607c713a30db818107ca0581269f059a2 /drivers/char | |
parent | b2b47c214f4e85ce3968120d42e8b18eccb4f4e3 (diff) |
lguest: documentation III: Drivers
Documentation: The Drivers
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/char')
-rw-r--r-- | drivers/char/hvc_lguest.c | 77 |
1 files changed, 74 insertions, 3 deletions
diff --git a/drivers/char/hvc_lguest.c b/drivers/char/hvc_lguest.c index e7b889e404a..1de8967cce0 100644 --- a/drivers/char/hvc_lguest.c +++ b/drivers/char/hvc_lguest.c @@ -1,6 +1,19 @@ -/* Simple console for lguest. +/*D:300 + * The Guest console driver * - * Copyright (C) 2006 Rusty Russell, IBM Corporation + * This is a trivial console driver: we use lguest's DMA mechanism to send + * bytes out, and register a DMA buffer to receive bytes in. It is assumed to + * be present and available from the very beginning of boot. + * + * Writing console drivers is one of the few remaining Dark Arts in Linux. + * Fortunately for us, the path of virtual consoles has been well-trodden by + * the PowerPC folks, who wrote "hvc_console.c" to generically support any + * virtual console. We use that infrastructure which only requires us to write + * the basic put_chars and get_chars functions and call the right register + * functions. + :*/ + +/* Copyright (C) 2006 Rusty Russell, IBM Corporation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,49 +34,81 @@ #include <linux/lguest_bus.h> #include "hvc_console.h" +/*D:340 This is our single console input buffer, with associated "struct + * lguest_dma" referring to it. Note the 0-terminated length array, and the + * use of physical address for the buffer itself. */ static char inbuf[256]; static struct lguest_dma cons_input = { .used_len = 0, .addr[0] = __pa(inbuf), .len[0] = sizeof(inbuf), .len[1] = 0 }; +/*D:310 The put_chars() callback is pretty straightforward. + * + * First we put the pointer and length in a "struct lguest_dma": we only have + * one pointer, so we set the second length to 0. Then we use SEND_DMA to send + * the data to (Host) buffers attached to the console key. Usually a device's + * key is a physical address within the device's memory, but because the + * console device doesn't have any associated physical memory, we use the + * LGUEST_CONSOLE_DMA_KEY constant (aka 0). */ static int put_chars(u32 vtermno, const char *buf, int count) { struct lguest_dma dma; - /* FIXME: what if it's over a page boundary? */ + /* FIXME: DMA buffers in a "struct lguest_dma" are not allowed + * to go over page boundaries. This never seems to happen, + * but if it did we'd need to fix this code. */ dma.len[0] = count; dma.len[1] = 0; dma.addr[0] = __pa(buf); lguest_send_dma(LGUEST_CONSOLE_DMA_KEY, &dma); + /* We're expected to return the amount of data we wrote: all of it. */ return count; } +/*D:350 get_chars() is the callback from the hvc_console infrastructure when + * an interrupt is received. + * + * Firstly we see if our buffer has been filled: if not, we return. The rest + * of the code deals with the fact that the hvc_console() infrastructure only + * asks us for 16 bytes at a time. We keep a "cons_offset" variable for + * partially-read buffers. */ static int get_chars(u32 vtermno, char *buf, int count) { static int cons_offset; + /* Nothing left to see here... */ if (!cons_input.used_len) return 0; + /* You want more than we have to give? Well, try wanting less! */ if (cons_input.used_len - cons_offset < count) count = cons_input.used_len - cons_offset; + /* Copy across to their buffer and increment offset. */ memcpy(buf, inbuf + cons_offset, count); cons_offset += count; + + /* Finished? Zero offset, and reset cons_input so Host will use it + * again. */ if (cons_offset == cons_input.used_len) { cons_offset = 0; cons_input.used_len = 0; } return count; } +/*:*/ static struct hv_ops lguest_cons = { .get_chars = get_chars, .put_chars = put_chars, }; +/*D:320 Console drivers are initialized very early so boot messages can go + * out. At this stage, the console is output-only. Our driver checks we're a + * Guest, and if so hands hvc_instantiate() the console number (0), priority + * (0), and the struct hv_ops containing the put_chars() function. */ static int __init cons_init(void) { if (strcmp(paravirt_ops.name, "lguest") != 0) @@ -73,21 +118,46 @@ static int __init cons_init(void) } console_initcall(cons_init); +/*D:370 To set up and manage our virtual console, we call hvc_alloc() and + * stash the result in the private pointer of the "struct lguest_device". + * Since we never remove the console device we never need this pointer again, + * but using ->private is considered good form, and you never know who's going + * to copy your driver. + * + * Once the console is set up, we bind our input buffer ready for input. */ static int lguestcons_probe(struct lguest_device *lgdev) { int err; + /* The first argument of hvc_alloc() is the virtual console number, so + * we use zero. The second argument is the interrupt number. + * + * The third argument is a "struct hv_ops" containing the put_chars() + * and get_chars() pointers. The final argument is the output buffer + * size: we use 256 and expect the Host to have room for us to send + * that much. */ lgdev->private = hvc_alloc(0, lgdev_irq(lgdev), &lguest_cons, 256); if (IS_ERR(lgdev->private)) return PTR_ERR(lgdev->private); + /* We bind a single DMA buffer at key LGUEST_CONSOLE_DMA_KEY. + * "cons_input" is that statically-initialized global DMA buffer we saw + * above, and we also give the interrupt we want. */ err = lguest_bind_dma(LGUEST_CONSOLE_DMA_KEY, &cons_input, 1, lgdev_irq(lgdev)); if (err) printk("lguest console: failed to bind buffer.\n"); return err; } +/* Note the use of lgdev_irq() for the interrupt number. We tell hvc_alloc() + * to expect input when this interrupt is triggered, and then tell + * lguest_bind_dma() that is the interrupt to send us when input comes in. */ +/*D:360 From now on the console driver follows standard Guest driver form: + * register_lguest_driver() registers the device type and probe function, and + * the probe function sets up the device. + * + * The standard "struct lguest_driver": */ static struct lguest_driver lguestcons_drv = { .name = "lguestcons", .owner = THIS_MODULE, @@ -95,6 +165,7 @@ static struct lguest_driver lguestcons_drv = { .probe = lguestcons_probe, }; +/* The standard init function */ static int __init hvc_lguest_init(void) { return register_lguest_driver(&lguestcons_drv); |