I don't know if anyone else would find this useful or not. This patch adds support to log all console IO to a buffer. The dmesg command can then display the buffer utilizing more-style paging or dump it to a remote host via TCP.
I used this to debug my PCI-e InfiniBand sanboot setup. Vintage serial ports weren't available on the computer so this was the easiest option. (Although trying out QEMU's pci-e pass-through was a close second.) The entire code is modularized using gPXE's awesome linker table methodology and this patch sets it to disabled in the main config by default. Matthew Lowe --------------------- >From f5102645798f3f6422fc79b0919523e6c6707bcd Mon Sep 17 00:00:00 2001 From: Matthew Lowe <[email protected]> Date: Sat, 3 Jul 2010 16:24:58 +0000 Subject: [PATCH] Added command "dmesg" to provide a means of logging and replaying console output. Dmesg can display output via pages directly through the command line interface, or it can dump the entire buffer to a remote host via a TCP connection. There are three components to dmesg: - dmesg memory buffer (core/dmesg.c) - dmesg console driver (core/dmesg_console.c) - dmesg userspace command (hci/commandline/dmesg_cmd.c) Signed-off-by: Matthew Lowe <[email protected]> --- src/config/config.c | 5 + src/config/general.h | 4 + src/core/dmesg.c | 170 ++++++++++++++++ src/core/dmesg_console.c | 56 ++++++ src/hci/commands/dmesg_cmd.c | 439 ++++++++++++++++++++++++++++++++++++++++++ src/include/gpxe/dmesg.h | 57 ++++++ src/include/gpxe/errfile.h | 2 + 7 files changed, 733 insertions(+), 0 deletions(-) create mode 100644 src/core/dmesg.c create mode 100644 src/core/dmesg_console.c create mode 100644 src/hci/commands/dmesg_cmd.c create mode 100644 src/include/gpxe/dmesg.h diff --git a/src/config/config.c b/src/config/config.c index a6e7622..7b44564 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -252,6 +252,11 @@ REQUIRE_OBJECT ( gdbidt ); REQUIRE_OBJECT ( gdbudp ); REQUIRE_OBJECT ( gdbstub_cmd ); #endif +#ifdef DMESG +REQUIRE_OBJECT ( dmesg ); +REQUIRE_OBJECT ( dmesg_console ); +REQUIRE_OBJECT ( dmesg_cmd ); +#endif /* * Drag in objects that are always required, but not dragged in via diff --git a/src/config/general.h b/src/config/general.h index bfab5b6..244acaf 100644 --- a/src/config/general.h +++ b/src/config/general.h @@ -144,6 +144,10 @@ FILE_LICENCE ( GPL2_OR_LATER ); #undef GDBSERIAL /* Remote GDB debugging over serial */ #undef GDBUDP /* Remote GDB debugging over UDP * (both may be set) */ +#undef DMESG /* Console memory buffer for debugging */ +#define DMESG_PAGE_SIZE 10240 /* Memory buffer page size */ +#define DMESG_PAGES 20 /* Total memory buffer size is + * pages * page_size */ #include <config/local/general.h> diff --git a/src/core/dmesg.c b/src/core/dmesg.c new file mode 100644 index 0000000..409cbc4 --- /dev/null +++ b/src/core/dmesg.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2010 Matthew Lowe <[email protected]>. + * + * 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 the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <curses.h> +#include <errno.h> +#include <config/general.h> +#include <gpxe/dmesg.h> +#include <gpxe/init.h> +#include <gpxe/umalloc.h> +#include <gpxe/errfile.h> + + +FILE_LICENCE ( GPL2_OR_LATER ); + +struct dmesg_buffer dmesg_buffer = { + .buffer = NULL, + .pos = NULL, + .size = 0, + .locked = FALSE, +}; + +/** + * + * Cycle data buffer by one page + * + */ +void shift_one_page () +{ + int len = (int) ( dmesg_buffer.pos + 1 - dmesg_buffer.buffer) - DMESG_PAGE_SIZE; + + if ( len <= 0) + return; + + /* Copy buffer memory and include null terminator */ + memcpy ( dmesg_buffer.buffer, dmesg_buffer.buffer + DMESG_PAGE_SIZE, + len ); + + dmesg_buffer.pos -= DMESG_PAGE_SIZE; +} + +/** + * + * Write character to buffer + * @v ch Character to write + * + */ +void dmesg_putc ( int ch ) { + + /* Ensure the buffer is not locked and is allocated */ + if ( dmesg_buffer.locked || !dmesg_buffer.size ) + return; + + /* Remap delete to backspace */ + if ( ch == 0x7f ) + ch = 0x08; + + /* If we're out of buffer space (include NULL terminator), + * cycle the buffer one page */ + if ( (unsigned int) ( dmesg_buffer.pos + 1 - dmesg_buffer.buffer ) == + dmesg_buffer.size ) + shift_one_page (); + + *(dmesg_buffer.pos++) = (char) ch; + + /* Null terminate buffer for convenience, but do not increment write position */ + *dmesg_buffer.pos = '\0'; + +} + +/** + * Dmesg getc console driver function + * @ret ch Always 0, no input to return + */ +int dmesg_getc ( void ) { + return 0; +} + +/** + * Dmesg ischar console driver function + * @ret status Always 0, no input to return + */ +int dmesg_ischar ( void ) { + return 0; +} + +/** + * Initialize dmesg memory buffer + */ +static void dmesg_init ( void ) { + + /* If buffer is already initialized, do not initialize again */ + if ( dmesg_buffer.buffer ) + return; + +#if DMESG_PAGES < 2 +# error "DMESG_PAGES must be at least 2" +#endif + +#if DMESG_PAGE_SIZE < 2 +# error "DMESG_PAGES must be at least 2" +#endif + + dmesg_buffer.user_buffer = umalloc ( DMESG_PAGE_SIZE * DMESG_PAGES ); + + if ( !dmesg_buffer.user_buffer ) + { + DBGC ( dmesg_buffer, "Could not initiliaze dmesg console buffer: %s\n", strerror (-ENOMEM) ); + goto out; + } + + dmesg_buffer.buffer = (char *) user_to_phys ( dmesg_buffer.user_buffer, 0 ); + + dmesg_buffer.size = DMESG_PAGE_SIZE * DMESG_PAGES; + dmesg_buffer.pos = dmesg_buffer.buffer; + + out: + return; +} + +/** + * Free dmesg buffer + * @v __unused Unused + */ +static void dmesg_fini ( int flags __unused ) { + + /* Sanity check - don't free the buffer while in use */ + if ( dmesg_buffer.locked ) + { + DBGC ( dmesg_buffer, "Could not free dmesg buffer because buffer is locked.\n" ); + return; + } + + /* Only free buffer if one was allocated */ + if ( dmesg_buffer.user_buffer ) + ufree ( dmesg_buffer.user_buffer ); + + /* Zero memory buffer access structure */ + memset ( &dmesg_buffer, 0, sizeof (dmesg_buffer) ); +} + +/** + * Dmesg driver initialization function + * + * Initialize memory buffer at the same time as the serial driver. + * Why does the serial driver have it's own INIT level? + */ +struct init_fn dmesg_init_fn __init_fn ( INIT_SERIAL ) = { + .initialise = dmesg_init, +}; + +/** Dmesg driver startup (and shutdown) function */ +struct startup_fn dmesg_startup_fn __startup_fn ( STARTUP_EARLY ) = { + .shutdown = dmesg_fini, +}; diff --git a/src/core/dmesg_console.c b/src/core/dmesg_console.c new file mode 100644 index 0000000..fac2617 --- /dev/null +++ b/src/core/dmesg_console.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2010 Matthew Lowe <[email protected]>. + * + * 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 the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <console.h> +#include <gpxe/init.h> +#include <gpxe/dmesg.h> + +/** @file + * + * Dmesg console + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +struct console_driver dmesg_console __console_driver; + +/** + * Enable console + */ +static void dmesg_console_init ( void ) { + dmesg_console.disabled = 0; +} + +/** + * Dmesg console driver + */ +struct console_driver dmesg_console __console_driver = { + .putchar = dmesg_putc, + .getchar = dmesg_getc, + .iskey = dmesg_ischar, + .disabled = 1, +}; + +/** + * Dmesg console initialization function + */ +struct init_fn dmesg_console_init_fn __init_fn ( INIT_CONSOLE ) = { + .initialise = dmesg_console_init, +}; diff --git a/src/hci/commands/dmesg_cmd.c b/src/hci/commands/dmesg_cmd.c new file mode 100644 index 0000000..bdd4ff2 --- /dev/null +++ b/src/hci/commands/dmesg_cmd.c @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2010 Matthew Lowe <[email protected]>. + * + * 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 the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <getopt.h> +#include <curses.h> +#include <console.h> +#include <byteswap.h> +#include <gpxe/process.h> +#include <gpxe/errfile.h> +#include <gpxe/command.h> +#include <gpxe/xfer.h> +#include <gpxe/tcpip.h> +#include <gpxe/open.h> +#include <gpxe/job.h> +#include <gpxe/monojob.h> +#include <gpxe/dmesg.h> + +/** @file + * + * Dmesg userpace command + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +extern struct dmesg_buffer dmesg_buffer; + +/** + * + * Dump dmesg buffer and page output + * + * @v rows Rows per page + * @v cols Columns per row + * @ret rc Return status code + */ +static int page_dmesg (int rows, int cols) +{ + char *ls, *pos, c; + int lines; + + /* Ensure the buffer is not locked */ + if ( dmesg_buffer.locked ) + return -EADDRINUSE; + + if ( !dmesg_buffer.buffer ) + return -EINVAL; + + dmesg_buffer.locked = TRUE; + + ls = pos = dmesg_buffer.buffer; + + /* Begin parsing buffer, one line at a time */ + while ( pos < dmesg_buffer.pos ) + { + pos = strchr (ls, '\n'); + + if ( !pos ) + pos = dmesg_buffer.pos; + + /* Auto-wrap output if characters before NL + * is greater than desired columns */ + if ((pos - ls) > cols) + pos = ls + cols; + + /* Null terminate line */ + c = *pos; + *pos = '\0'; + + printf ("%s\n",ls); + + /* Remove null terminator */ + *pos = c; + + if (c == '\n') + pos++; + + ls = pos; + lines ++; + + /* If we've displayed one page out output, wait for user + * input to continue */ + if (lines == (rows - 1)) + { + lines = 0; + printf ( "--More--" ); + c = getchar(); + + /* Erase prompt - incase we are logging output somewhere else + * such as a serial console */ + printf ( "\r \r" ); + + if (c == 'q') + { + dmesg_buffer.locked = FALSE; + return -ECANCELED; + } + + } + } + + dmesg_buffer.locked = FALSE; + return PXENV_STATUS_SUCCESS; +} + +/** TCP Sender */ +struct tcpsender { + /** Reference count for this object */ + struct refcnt refcnt; + + /** Job control interface */ + struct job_interface job; + + /** Process control interface */ + struct process process; + + /** TCP Socket */ + struct xfer_interface socket; + struct sockaddr_tcpip server; + + /** Data Information */ + char *pos; + char *end; +}; + +/** + * Cleanup all interfaces and terminate process + * + * @v tcsender TCPsender session + * @v rc Return status code + */ +static void tcpsender_finished ( struct tcpsender *tcpsender, int rc ) { + + /* Remove process */ + process_del ( &tcpsender->process ); + + /* Block further incoming messages */ + job_nullify ( &tcpsender->job ); + xfer_nullify ( &tcpsender->socket ); + + /* Free resources and close interfaces */ + xfer_close ( &tcpsender->socket, rc ); + job_done ( &tcpsender->job, rc ); +} + +/** + * Gracefully handle socket closure + * + * @v socket TCP socket + * @v rc Return status code + */ +static void tcpsender_xfer_close ( struct xfer_interface *socket, int rc ) { + struct tcpsender *tcpsender = + container_of ( socket, struct tcpsender, socket ); + + /* Terminate tcpsender */ + tcpsender_finished ( tcpsender, rc ); +} + +/** + * Tcpsender process + * + * @v process Tcpsender process + */ +static void tcpsender_step ( struct process *process ) { + int rc; + int ws; + + struct tcpsender *tcpsender = + container_of ( process, struct tcpsender, process ); + + /* Set processing positions in dmesg buffer if they are not set yet */ + if ( !tcpsender->pos ) + tcpsender->pos = dmesg_buffer.buffer; + + if ( !tcpsender->end ) + tcpsender->end = dmesg_buffer.pos; + + /* Wait for TCP connection to establish or TX queue to empty */ + if ( ( ws = xfer_window ( &tcpsender->socket ) ) ) + { + /* If we have no more data terminate the job */ + if ( tcpsender->pos == tcpsender->end) + { + tcpsender_finished ( tcpsender, PXENV_STATUS_SUCCESS ); + return; + } + + if ( ( tcpsender->pos + ws ) > tcpsender->end ) + ws = (int) ( tcpsender->end - tcpsender->pos ); + + + rc = xfer_deliver_raw ( &tcpsender->socket, tcpsender->pos, ws ); + tcpsender->pos += ws; + + if ( rc ) + tcpsender_finished ( tcpsender, rc ); + } +} + +/** + * Gracefully handle kill request + * @v job Job interface + */ +static void tcpsender_job_kill ( struct job_interface *job ) { + struct tcpsender *tcpsender = + container_of ( job, struct tcpsender, job ); + + /* Terminate tcpsender */ + tcpsender_finished ( tcpsender, -ECANCELED ); +} + +/* Tcpsender job control interface */ +static struct job_interface_operations tcpsender_job_operations = { + .done = ignore_job_done, + .kill = tcpsender_job_kill, + .progress = ignore_job_progress, +}; + +/* Tcpsender xfer interface */ +static struct xfer_interface_operations tcpsender_socket_operations = { + .close = tcpsender_xfer_close, + .vredirect = xfer_vreopen, + .window = unlimited_xfer_window, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = xfer_deliver_as_raw, + .deliver_raw = ignore_xfer_deliver_raw, + }; + +/** + * Free tcpsender session + * @v refcnt Reference counter + */ +static void tcpsender_free ( struct refcnt *refcnt ) { + struct tcpsender *tcpsender = + container_of ( refcnt, struct tcpsender, refcnt ); + + free ( tcpsender ); +} + +/** + * Send dmesg buffer to remote host + * @v host Remote host + * @v port Destination port + * @ret rc Return status + */ +static int dmesg_tcp (char *host, char *port) +{ + struct tcpsender *tcpsender; + int rc; + + /* Sanity check host and port parameters */ + if (!host || !port) + return -EINVAL; + + /* Ensure the dmesg buffer is available */ + if ( dmesg_buffer.locked ) + { + printf ("DMESG buffer is currently locked.\n"); + return -EADDRINUSE; + } + + dmesg_buffer.locked = TRUE; + + /* Allocate and initialize tcpsender session */ + if ( ! ( tcpsender = zalloc ( sizeof(*tcpsender) ) ) ) + { + dmesg_buffer.locked = FALSE; + return -ENOMEM; + } + + tcpsender->refcnt.free = tcpsender_free; + + job_init ( &tcpsender->job, &tcpsender_job_operations, + &tcpsender->refcnt ); + + xfer_init ( &tcpsender->socket, &tcpsender_socket_operations, + &tcpsender->refcnt ); + + process_init ( &tcpsender->process, tcpsender_step, &tcpsender->refcnt ); + + tcpsender->server.st_port = htons ( strtoul ( port, NULL, 0 ) ); + + /* Open socket to remote host */ + if ( ( rc = xfer_open_named_socket ( &tcpsender->socket, SOCK_STREAM, + (struct sockaddr *) &tcpsender->server, + host, NULL ) ) != 0 ) + goto err; + + /* Attach parent interface */ + job_plug_plug ( &tcpsender->job, &monojob ); + + /* Wait for send to complete */ + rc = monojob_wait ( "Sending" ); + + ref_put ( &tcpsender->refcnt ); + + dmesg_buffer.locked = FALSE; + + return rc; + + err: + tcpsender_finished ( tcpsender, rc ); + ref_put ( &tcpsender->refcnt ); + + dmesg_buffer.locked = FALSE; + + return rc; +} + +/** + * Display command usage + */ +static void dmesg_print_syntax ( ) { + printf ( "Usage:\n" + " dmesg [OPTIONS]\n" + " -h, --help - Show this help message\n" + " -n, --nopage - Do not page output\n" + " -r rows, --rows rows - Print [rows] rows per page\n" + " -c cols, --cols cols - Print [cols] characters per row\n" + " -t host:port, --tcp ip:port - Dump dmesg output to [ip]:[port] via TCP\n" + "\n" + "Display debug messages\n"); +} + +/** + * dmesg command + * @v argc Argument count + * @v argv Argument list + * @ret rc Exit code + */ +static int dmesg_exec ( int argc, char **argv ) { + + static struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "rows", required_argument, NULL, 'r' }, + { "cols", required_argument, NULL, 'c' }, + { "nopage", no_argument, NULL, 'n' }, + { "tcp", required_argument, NULL, 't'}, + { NULL, 0, NULL, 0 }, + }; + + int c; + int rows = 25; + int cols = 80; + char *port; + + /* Parse options */ + while ( ( c = getopt_long ( argc, argv, "hr:c:nt:", + longopts, NULL ) ) >= 0 ) { + switch ( c ) { + case 'r': + /* Set number of rows per page */ + rows = strtoul ( optarg, NULL, 0 ); + + /* Sanity check */ + if ( rows < 2 ) + { + dmesg_print_syntax (); + return -EINVAL; + } + + break; + case 'c': + /* Set number of cols per row */ + cols = strtoul ( optarg, NULL, 10 ); + + /* Sanity check */ + if ( cols < 1 ) + { + dmesg_print_syntax (); + return -EINVAL; + } + + break; + case 'n': + /* Do not page output, just dump it all to console */ + + /* Ensure the buffer is not locked */ + if ( dmesg_buffer.locked ) + return -EADDRINUSE; + + /* Lock buffer and dump contents to console */ + dmesg_buffer.locked = TRUE; + printf ( "%s", dmesg_buffer.buffer ); + dmesg_buffer.locked = FALSE; + + return PXENV_STATUS_SUCCESS; + case 't': + /* Send debug output to remote host via TCP */ + port = strchr ( optarg, ':' ); + + if ( !port || !*(port+1) ) + { + printf ( "Error: No port specified.\n" ); + dmesg_print_syntax (); + return -EINVAL; + } + *(port++) = '\0'; + + return dmesg_tcp (optarg, port); + case 'h': + /* Display help text */ + default: + /* Unrecognised/invalid option */ + dmesg_print_syntax (); + + return -EINVAL; + } + } + + return (page_dmesg (rows,cols)); +} + +/** Dmesg command */ +struct command time_command __command = { + .name = "dmesg", + .exec = dmesg_exec, +}; + diff --git a/src/include/gpxe/dmesg.h b/src/include/gpxe/dmesg.h new file mode 100644 index 0000000..ae83d75 --- /dev/null +++ b/src/include/gpxe/dmesg.h @@ -0,0 +1,57 @@ +#ifndef _GPXE_DMESG_H +#define _GPXE_DMESG_H + +/* + * Copyright (C) 2010 Matthew Lowe <[email protected]>. + * + * 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 the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** + * @file + * + * Dmesg driver functions + * + */ + +/* Include user memory pointer type */ +#include <gpxe/umalloc.h> + + + +/* Container structure that describes console memory buffer */ +struct dmesg_buffer { + userptr_t user_buffer; + + /* Physical buffer locations */ + char *buffer; + char *pos; + + unsigned int size; + + /* Basic access control */ + int locked; +}; + +/* Console interface prototypes */ +extern void dmesg_putc ( int ch ); +extern int dmesg_getc ( void ); +extern int dmesg_ischar ( void ); + + +#endif /* _GPXE_DMESG_H */ diff --git a/src/include/gpxe/errfile.h b/src/include/gpxe/errfile.h index a17da90..f0e6afe 100644 --- a/src/include/gpxe/errfile.h +++ b/src/include/gpxe/errfile.h @@ -55,6 +55,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define ERRFILE_bitmap ( ERRFILE_CORE | 0x000f0000 ) #define ERRFILE_base64 ( ERRFILE_CORE | 0x00100000 ) #define ERRFILE_base16 ( ERRFILE_CORE | 0x00110000 ) +#define ERRFILE_dmesg ( ERRFILE_CORE | 0x00120000 ) #define ERRFILE_eisa ( ERRFILE_DRIVER | 0x00000000 ) #define ERRFILE_isa ( ERRFILE_DRIVER | 0x00010000 ) @@ -210,6 +211,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define ERRFILE_login_ui ( ERRFILE_OTHER | 0x00170000 ) #define ERRFILE_ib_srpboot ( ERRFILE_OTHER | 0x00180000 ) #define ERRFILE_iwmgmt ( ERRFILE_OTHER | 0x00190000 ) +#define ERRFILE_dmesg_cmd ( ERRFILE_OTHER | 0x001A0000 ) /** @} */ -- 1.7.1 _______________________________________________ gPXE mailing list [email protected] http://etherboot.org/mailman/listinfo/gpxe
