mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 10:11:27 +01:00
bitkeeper revision 1.1098.1.1 (4107c16f_jhuAGUiMBcotuiFhO6dHQ)
Add a simple debugger for Xen and domain 0. This isn't anything like as complete as PDB; it can't access any userspace other than the current process's and it can't make any changes to a running process. On the other hand, it's probably slightly less fragile. The code's a lot cleaner, as well.
This commit is contained in:
1 parent
2faa66991b
commit
b69f92f301
9 files changed
+471
-20
No files matched your search
@@ -449,10 +449,12 @@
|
||||
3ddb79bcecupHj56ZbTa3B0FxDowMg xen/arch/x86/x86_32/entry.S
|
||||
3ddb79bcHwuCQDjBICDTSis52hWguw xen/arch/x86/x86_32/mm.c
|
||||
3ddb79bc4nTpGQOe6_-MbyZzkhlhFQ xen/arch/x86/x86_32/usercopy.c
|
||||
4107c15e_NqNYew2EXroXz2mgTAMWQ xen/arch/x86/x86_32/xdb_trap.S
|
||||
3ddb79bcOMCu9-5mKpjIh5d0qqBDPg xen/arch/x86/x86_32/xen.lds
|
||||
40e96d3aLDI-nViMuYneD7VKYlZrVg xen/arch/x86/x86_64/entry.S
|
||||
40e96d3ahBTZqbTViInnq0lM03vs7A xen/arch/x86/x86_64/usercopy.c
|
||||
40e96d3akN3Hu_J5Bk-WXD8OGscrYQ xen/arch/x86/x86_64/xen.lds
|
||||
4107c15e-VmEcLsE-7JCXZaabI8C7A xen/arch/x86/xdb.c
|
||||
3ddb79bdff-gj-jFGKjOejeHLqL8Lg xen/common/Makefile
|
||||
3e397e66AyyD5fYraAySWuwi9uqSXg xen/common/ac_timer.c
|
||||
4022a73c_BbDFd2YJ_NQYVvKX5Oz7w xen/common/debug-linux.c
|
||||
|
||||
@@ -38,6 +38,7 @@ smh22@boulderdash.cl.cam.ac.uk
|
||||
smh22@labyrinth.cl.cam.ac.uk
|
||||
smh22@tempest.cl.cam.ac.uk
|
||||
smh22@uridium.cl.cam.ac.uk
|
||||
sos22@donkeykong.cl.cam.ac.uk
|
||||
sos22@labyrinth.cl.cam.ac.uk
|
||||
tlh20@elite.cl.cam.ac.uk
|
||||
tlh20@labyrinth.cl.cam.ac.uk
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
.global trap_to_xendbg
|
||||
.extern __trap_to_xendbg
|
||||
|
||||
#define SAVE_ALL_NOSEGREGS \
|
||||
pushw $0; \
|
||||
pushw %gs; \
|
||||
pushw $0; \
|
||||
pushw %fs; \
|
||||
pushw $0; \
|
||||
pushw %es; \
|
||||
pushw $0; \
|
||||
pushw %ds; \
|
||||
pushl %eax; \
|
||||
pushl %ebp; \
|
||||
pushl %edi; \
|
||||
pushl %esi; \
|
||||
pushl %edx; \
|
||||
pushl %ecx; \
|
||||
pushl %ebx;
|
||||
|
||||
// Save the register state and call __trap_to_xendbg
|
||||
trap_to_xendbg:
|
||||
pushw $0
|
||||
pushw %ss
|
||||
pushl %esp //We'll fix this up later, in __trap_to_xendbg, by adding 8
|
||||
pushf
|
||||
pushw $0
|
||||
pushw %cs
|
||||
pushl 16(%esp)
|
||||
1: pushl $0 // Orig_eax
|
||||
SAVE_ALL_NOSEGREGS
|
||||
pushl %esp
|
||||
call __trap_to_xendbg
|
||||
add $72, %esp
|
||||
ret
|
||||
@@ -0,0 +1,378 @@
|
||||
/* Simple hacked-up version of pdb for us in post-mortem debugging of
|
||||
Xen and domain 0. This should be a little cleaner, hopefully. Note
|
||||
that we can't share a serial line with PDB. */
|
||||
#include <xen/lib.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <xen/serial.h>
|
||||
#include <asm/irq.h>
|
||||
#include <xen/spinlock.h>
|
||||
|
||||
/* Printk isn't particularly safe just after we've trapped to the
|
||||
debugger. so avoid it. */
|
||||
#define dbg_printk(...)
|
||||
|
||||
static int
|
||||
xendbg_serhnd = -1;
|
||||
|
||||
static void
|
||||
xendbg_put_char(u8 data)
|
||||
{
|
||||
serial_putc(xendbg_serhnd, data);
|
||||
}
|
||||
|
||||
static u8
|
||||
xendbg_get_char(void)
|
||||
{
|
||||
u8 ch;
|
||||
extern unsigned char __serial_getc(int handle);
|
||||
ch = __serial_getc(xendbg_serhnd);
|
||||
return ch;
|
||||
}
|
||||
|
||||
static int
|
||||
hex_char_val(unsigned char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
else
|
||||
BUG();
|
||||
}
|
||||
|
||||
static unsigned char
|
||||
val_to_hex_char(unsigned val)
|
||||
{
|
||||
if (val < 10)
|
||||
return val + '0';
|
||||
else if (val < 16)
|
||||
return val - 10 + 'a';
|
||||
else
|
||||
BUG();
|
||||
}
|
||||
|
||||
/* Receive a command. Returns -1 on csum error, 0 otherwise. */
|
||||
/* Does not acknowledge. */
|
||||
static int
|
||||
attempt_receive_packet(char *recv_buf)
|
||||
{
|
||||
int count;
|
||||
u8 csum;
|
||||
u8 received_csum;
|
||||
u8 ch;
|
||||
|
||||
/* Skip over everything up to the first '$' */
|
||||
while ((ch = xendbg_get_char()) != '$')
|
||||
;
|
||||
csum = 0;
|
||||
for (count = 0; count < 4096; count++) {
|
||||
ch = xendbg_get_char();
|
||||
if (ch == '#')
|
||||
break;
|
||||
recv_buf[count] = ch;
|
||||
csum += ch;
|
||||
}
|
||||
if (count == 4096) {
|
||||
dbg_printk("WARNING: GDB sent a stupidly big packet.\n");
|
||||
return -1;
|
||||
}
|
||||
recv_buf[count] = 0;
|
||||
received_csum = hex_char_val(xendbg_get_char()) * 16 +
|
||||
hex_char_val(xendbg_get_char());
|
||||
if (received_csum == csum) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Send a string of bytes to the debugger. */
|
||||
static void
|
||||
xendbg_send(const char *buf, int count)
|
||||
{
|
||||
int x;
|
||||
for (x = 0; x < count; x++)
|
||||
xendbg_put_char(buf[x]);
|
||||
}
|
||||
|
||||
/* Receive a command, discarding up to ten packets with csum
|
||||
* errors. Acknowledges all received packets. */
|
||||
static int
|
||||
receive_command(char *recv_buf)
|
||||
{
|
||||
int r;
|
||||
int count;
|
||||
|
||||
count = 0;
|
||||
do {
|
||||
r = attempt_receive_packet(recv_buf);
|
||||
if (r < 0)
|
||||
xendbg_send("-", 1);
|
||||
else
|
||||
xendbg_send("+", 1);
|
||||
count++;
|
||||
} while (r < 0 && count < 10);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void
|
||||
u32_to_hex_u8(unsigned char val, char *buf)
|
||||
{
|
||||
if (val >= 256)
|
||||
BUG();
|
||||
sprintf(buf, "%.02x\n", val);
|
||||
}
|
||||
|
||||
static void
|
||||
u32_to_hex_u32(unsigned val, char *buf)
|
||||
{
|
||||
sprintf(buf, "%.08x\n", val);
|
||||
}
|
||||
|
||||
static void
|
||||
xendbg_send_hex_u8(unsigned char val)
|
||||
{
|
||||
char buf[2];
|
||||
u32_to_hex_u8(val, buf);
|
||||
xendbg_send(buf, 2);
|
||||
}
|
||||
|
||||
static u8
|
||||
xendbg_reply_csum;
|
||||
|
||||
static void
|
||||
xendbg_start_reply(void)
|
||||
{
|
||||
xendbg_reply_csum = 0;
|
||||
xendbg_send("$", 1);
|
||||
}
|
||||
|
||||
static void
|
||||
xendbg_sendrep_data(const unsigned char *data, unsigned long len)
|
||||
{
|
||||
int x;
|
||||
|
||||
for (x = 0; x < len; x++) {
|
||||
xendbg_put_char(data[x]);
|
||||
xendbg_reply_csum += data[x];
|
||||
}
|
||||
}
|
||||
|
||||
/* Return 0 if the reply was successfully received, !0 otherwise. */
|
||||
static int
|
||||
xendbg_finish_reply(void)
|
||||
{
|
||||
char ch;
|
||||
|
||||
xendbg_send("#", 1);
|
||||
xendbg_send_hex_u8(xendbg_reply_csum);
|
||||
ch = xendbg_get_char();
|
||||
if (ch == '+')
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
xendbg_sendrep_hex_u8(unsigned val)
|
||||
{
|
||||
char buf[2];
|
||||
u32_to_hex_u8(val, buf);
|
||||
xendbg_sendrep_data(buf, 2);
|
||||
}
|
||||
|
||||
static void
|
||||
xendbg_sendrep_hex_u32(unsigned val)
|
||||
{
|
||||
char buf[8];
|
||||
u32_to_hex_u32(val, buf);
|
||||
xendbg_sendrep_data(buf, 8);
|
||||
}
|
||||
|
||||
static void
|
||||
xendbg_sendrep_hex_u32_le(unsigned val)
|
||||
{
|
||||
val = (((val >> 0) & 0xff) << 24) |
|
||||
(((val >> 8) & 0xff) << 16) |
|
||||
(((val >> 16) & 0xff) << 8) |
|
||||
(((val >> 24) & 0xff) << 0);
|
||||
xendbg_sendrep_hex_u32(val);
|
||||
}
|
||||
|
||||
static int
|
||||
handle_memory_read_command(unsigned long addr, unsigned long length)
|
||||
{
|
||||
int x;
|
||||
unsigned char val;
|
||||
int r;
|
||||
unsigned old_s_limit;
|
||||
|
||||
dbg_printk("Memory read starting at %lx, length %lx.\n", addr,
|
||||
length);
|
||||
old_s_limit = current->addr_limit.seg;
|
||||
current->addr_limit.seg = ~0;
|
||||
xendbg_start_reply();
|
||||
for (x = 0; x < length; x++) {
|
||||
r = copy_from_user(&val, (void *)(addr + x), 1);
|
||||
if (r != 0) {
|
||||
dbg_printk("Error reading from %lx.\n", addr + x);
|
||||
break;
|
||||
}
|
||||
xendbg_sendrep_hex_u8(val);
|
||||
}
|
||||
if (x == 0)
|
||||
xendbg_sendrep_data("E05", 3);
|
||||
dbg_printk("Read done.\n");
|
||||
current->addr_limit.seg = old_s_limit;
|
||||
return xendbg_finish_reply();
|
||||
}
|
||||
|
||||
static int
|
||||
xendbg_send_reply(const char *buf)
|
||||
{
|
||||
xendbg_start_reply();
|
||||
xendbg_sendrep_data(buf, strlen(buf));
|
||||
return xendbg_finish_reply();
|
||||
}
|
||||
|
||||
static int
|
||||
handle_register_read_command(struct pt_regs *regs)
|
||||
{
|
||||
xendbg_start_reply();
|
||||
xendbg_sendrep_hex_u32_le(regs->eax);
|
||||
xendbg_sendrep_hex_u32_le(regs->ecx);
|
||||
xendbg_sendrep_hex_u32_le(regs->edx);
|
||||
xendbg_sendrep_hex_u32_le(regs->ebx);
|
||||
xendbg_sendrep_hex_u32_le(regs->esp);
|
||||
xendbg_sendrep_hex_u32_le(regs->ebp);
|
||||
xendbg_sendrep_hex_u32_le(regs->esi);
|
||||
xendbg_sendrep_hex_u32_le(regs->edi);
|
||||
xendbg_sendrep_hex_u32_le(regs->eip);
|
||||
xendbg_sendrep_hex_u32_le(regs->eflags);
|
||||
xendbg_sendrep_hex_u32_le(regs->xcs);
|
||||
xendbg_sendrep_hex_u32_le(regs->xss);
|
||||
xendbg_sendrep_hex_u32_le(regs->xes);
|
||||
xendbg_sendrep_hex_u32_le(regs->xfs);
|
||||
xendbg_sendrep_hex_u32_le(regs->xgs);
|
||||
return xendbg_finish_reply();
|
||||
}
|
||||
|
||||
static unsigned long
|
||||
hex_to_int(const char *start, const char **end)
|
||||
{
|
||||
return simple_strtol(start, (char **)end, 16);
|
||||
}
|
||||
|
||||
static int
|
||||
process_command(const char *received_packet, struct pt_regs *regs)
|
||||
{
|
||||
const char *ptr;
|
||||
unsigned long addr, length;
|
||||
int retry;
|
||||
int counter;
|
||||
int resume = 0;
|
||||
|
||||
/* Repeat until gdb acks the reply */
|
||||
counter = 0;
|
||||
do {
|
||||
switch (received_packet[0]) {
|
||||
case 'g': /* Read registers */
|
||||
retry = handle_register_read_command(regs);
|
||||
break;
|
||||
case 'm': /* Read memory */
|
||||
addr = hex_to_int(received_packet + 1, &ptr);
|
||||
if (ptr == received_packet + 1 ||
|
||||
ptr[0] != ',') {
|
||||
xendbg_send_reply("E03");
|
||||
return 0;
|
||||
}
|
||||
length = hex_to_int(ptr + 1, &ptr);
|
||||
if (ptr[0] != 0) {
|
||||
xendbg_send_reply("E04");
|
||||
return 0;
|
||||
}
|
||||
retry =
|
||||
handle_memory_read_command(addr,
|
||||
length);
|
||||
break;
|
||||
case 'G': /* Write registers */
|
||||
case 'M': /* Write memory */
|
||||
retry = xendbg_send_reply("E02");
|
||||
break;
|
||||
case 'D':
|
||||
resume = 1;
|
||||
retry = xendbg_send_reply("");
|
||||
break;
|
||||
case 'c': /* Resume at current address */
|
||||
case 's': /* Single step */
|
||||
case '?':
|
||||
retry = xendbg_send_reply("S01");
|
||||
break;
|
||||
default:
|
||||
retry = xendbg_send_reply("");
|
||||
break;
|
||||
}
|
||||
counter++;
|
||||
} while (retry == 1 && counter < 10);
|
||||
if (retry) {
|
||||
dbg_printk("WARNING: gdb disappeared when we were trying to send it a reply.\n");
|
||||
return 1;
|
||||
}
|
||||
return resume;
|
||||
}
|
||||
|
||||
void
|
||||
__trap_to_xendbg(struct pt_regs *regs)
|
||||
{
|
||||
int resume = 0;
|
||||
int r;
|
||||
static int xendbg_running;
|
||||
static char recv_buf[4096];
|
||||
|
||||
if (xendbg_serhnd < 0) {
|
||||
dbg_printk("Debugger not ready yet.\n");
|
||||
return;
|
||||
}
|
||||
/* We rely on our caller to ensure we're only on one processor
|
||||
* at a time... We should probably panic here, but given that
|
||||
* we're a debugger we should probably be a little tolerant of
|
||||
* things going wrong. */
|
||||
if (xendbg_running) {
|
||||
dbg_printk("WARNING WARNING WARNING: Avoiding recursive xendbg.\n");
|
||||
return;
|
||||
}
|
||||
xendbg_running = 1;
|
||||
/* trap_to_xendbg gets esp slightly wrong. Correct for this. */
|
||||
regs->esp += 8;
|
||||
|
||||
dbg_printk("Waiting for GDB to attach to XenDBG\n");
|
||||
/* Urgg... hope this is right... */
|
||||
while (resume == 0) {
|
||||
r = receive_command(recv_buf);
|
||||
if (r < 0) {
|
||||
dbg_printk("GDB disappeared, trying to resume Xen...\n");
|
||||
resume = 1;
|
||||
} else
|
||||
resume = process_command(recv_buf, regs);
|
||||
}
|
||||
xendbg_running = 0;
|
||||
}
|
||||
|
||||
void
|
||||
initialize_xendbg(void)
|
||||
{
|
||||
extern char opt_xendbg[];
|
||||
|
||||
if (!strcmp(opt_xendbg, "none"))
|
||||
return;
|
||||
xendbg_serhnd = parse_serial_handle(opt_xendbg);
|
||||
if (xendbg_serhnd == -1)
|
||||
panic("Can't parse %s as XDB serial info.\n", opt_xendbg);
|
||||
|
||||
/* Acknowledge any spurious GDB packets. */
|
||||
xendbg_put_char('+');
|
||||
|
||||
printk("Xendbg initialised.\n");
|
||||
}
|
||||
@@ -163,6 +163,8 @@ void domain_crash(void)
|
||||
BUG();
|
||||
}
|
||||
|
||||
extern void trap_to_xendbg(void);
|
||||
|
||||
void domain_shutdown(u8 reason)
|
||||
{
|
||||
struct domain *d;
|
||||
@@ -172,6 +174,8 @@ void domain_shutdown(u8 reason)
|
||||
extern void machine_restart(char *);
|
||||
extern void machine_halt(void);
|
||||
|
||||
trap_to_xendbg();
|
||||
|
||||
if ( reason == 0 )
|
||||
{
|
||||
printk("Domain 0 halted: Our work here is done.\n");
|
||||
|
||||
@@ -64,6 +64,8 @@ int opt_ignorebiostables=0;
|
||||
int opt_watchdog=0;
|
||||
/* opt_pdb: Name of serial port for Xen pervasive debugger (and enable pdb) */
|
||||
unsigned char opt_pdb[10] = "none";
|
||||
/* opt_pdb: Name of serial port for Xen debugger (and enable xendbg) */
|
||||
unsigned char opt_xendbg[10] = "none";
|
||||
/* opt_tbuf_size: trace buffer size (in pages) */
|
||||
unsigned int opt_tbuf_size = 10;
|
||||
/* opt_sched: scheduler - default to Borrowed Virtual Time */
|
||||
@@ -98,6 +100,7 @@ static struct {
|
||||
{ "ignorebiostables", OPT_BOOL, &opt_ignorebiostables },
|
||||
{ "watchdog", OPT_BOOL, &opt_watchdog },
|
||||
{ "pdb", OPT_STR, &opt_pdb },
|
||||
{ "xendbg", OPT_STR, &opt_xendbg },
|
||||
{ "tbuf_size", OPT_UINT, &opt_tbuf_size },
|
||||
{ "sched", OPT_STR, &opt_sched },
|
||||
{ "physdev_dom0_hide", OPT_STR, &opt_physdev_dom0_hide },
|
||||
@@ -108,6 +111,9 @@ static struct {
|
||||
};
|
||||
|
||||
|
||||
void initialize_xendbg(void);
|
||||
void trap_to_xendbg(void);
|
||||
|
||||
void cmain(multiboot_info_t *mbi)
|
||||
{
|
||||
struct domain *new_dom;
|
||||
@@ -163,6 +169,8 @@ void cmain(multiboot_info_t *mbi)
|
||||
|
||||
init_console();
|
||||
|
||||
initialize_xendbg();
|
||||
|
||||
/* HELLO WORLD --- start-of-day banner text. */
|
||||
printk(XEN_BANNER);
|
||||
printk(" http://www.cl.cam.ac.uk/netos/xen\n");
|
||||
@@ -291,6 +299,10 @@ void cmain(multiboot_info_t *mbi)
|
||||
|
||||
shadow_mode_init();
|
||||
|
||||
printk("Trapping to debugger.\n");
|
||||
trap_to_xendbg();
|
||||
printk("Trapped to debugger and came back.\n");
|
||||
|
||||
/*
|
||||
* We're going to setup domain0 using the module(s) that we stashed safely
|
||||
* above our heap. The second module, if present, is an initrd ramdisk.
|
||||
|
||||
@@ -115,6 +115,11 @@ extern void perfc_reset(unsigned char key, void *dev_id,
|
||||
struct pt_regs *regs);
|
||||
#endif
|
||||
|
||||
void do_panic_key(unsigned char key, void *dev_id, struct pt_regs *regs)
|
||||
{
|
||||
panic("Panic requested from console");
|
||||
}
|
||||
|
||||
void initialize_keytable(void)
|
||||
{
|
||||
add_key_handler('d', dump_registers, "dump registers");
|
||||
@@ -128,4 +133,5 @@ void initialize_keytable(void)
|
||||
add_key_handler('p', perfc_printall, "print performance counters");
|
||||
add_key_handler('P', perfc_reset, "reset performance counters");
|
||||
#endif
|
||||
add_key_handler('%', do_panic_key, "Panic Xen");
|
||||
}
|
||||
@@ -440,6 +440,8 @@ void console_endboot(int disable_vga)
|
||||
* **************************************************************
|
||||
*/
|
||||
|
||||
extern void trap_to_xendbg(void);
|
||||
|
||||
void panic(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
@@ -450,7 +452,9 @@ void panic(const char *fmt, ...)
|
||||
va_start(args, fmt);
|
||||
(void)vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
|
||||
trap_to_xendbg();
|
||||
|
||||
/* Spit out multiline message in one go. */
|
||||
spin_lock_irqsave(&console_lock, flags);
|
||||
__putstr("\n****************************************\n");
|
||||
|
||||
+28
-19
@@ -420,6 +420,30 @@ static int byte_matches(int handle, unsigned char *pc)
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned char __serial_getc(int handle)
|
||||
{
|
||||
uart_t *uart = &com[handle & SERHND_IDX];
|
||||
unsigned char c;
|
||||
|
||||
/* disable_irq() may have raced execution of uart_rx(). */
|
||||
while ( uart->rxbufp != uart->rxbufc )
|
||||
{
|
||||
c = uart->rxbuf[MASK_RXBUF_IDX(uart->rxbufc++)];
|
||||
if ( byte_matches(handle, &c) )
|
||||
return c;
|
||||
}
|
||||
|
||||
/* We now wait for the UART to receive a suitable character. */
|
||||
do {
|
||||
while ( (inb(uart->io_base + LSR) & LSR_DR) == 0 )
|
||||
barrier();
|
||||
c = inb(uart->io_base + RBR);
|
||||
}
|
||||
while ( !byte_matches(handle, &c) );
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
unsigned char serial_getc(int handle)
|
||||
{
|
||||
uart_t *uart = &com[handle & SERHND_IDX];
|
||||
@@ -434,26 +458,11 @@ unsigned char serial_getc(int handle)
|
||||
if ( byte_matches(handle, &c) )
|
||||
goto out;
|
||||
}
|
||||
|
||||
disable_irq(uart->irq);
|
||||
|
||||
/* disable_irq() may have raced execution of uart_rx(). */
|
||||
while ( uart->rxbufp != uart->rxbufc )
|
||||
{
|
||||
c = uart->rxbuf[MASK_RXBUF_IDX(uart->rxbufc++)];
|
||||
if ( byte_matches(handle, &c) )
|
||||
goto enable_and_out;
|
||||
}
|
||||
|
||||
/* We now wait for the UART to receive a suitable character. */
|
||||
do {
|
||||
while ( (inb(uart->io_base + LSR) & LSR_DR) == 0 )
|
||||
barrier();
|
||||
c = inb(uart->io_base + RBR);
|
||||
}
|
||||
while ( !byte_matches(handle, &c) );
|
||||
|
||||
enable_and_out:
|
||||
disable_irq(uart->irq);
|
||||
|
||||
c = __serial_getc(handle);
|
||||
|
||||
enable_irq(uart->irq);
|
||||
out:
|
||||
spin_unlock_irqrestore(&uart->lock, flags);
|
||||
|
||||