H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 1 | /* -*- linux-c -*- ------------------------------------------------------- * |
| 2 | * |
| 3 | * Copyright (C) 1991, 1992 Linus Torvalds |
| 4 | * Copyright 2007 rPath, Inc. - All Rights Reserved |
H. Peter Anvin | df7699c | 2009-04-01 18:13:46 -0700 | [diff] [blame] | 5 | * Copyright 2009 Intel Corporation; author H. Peter Anvin |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 6 | * |
| 7 | * This file is part of the Linux kernel, and is made available under |
| 8 | * the terms of the GNU General Public License version 2. |
| 9 | * |
| 10 | * ----------------------------------------------------------------------- */ |
| 11 | |
| 12 | /* |
Pekka Enberg | fa97bdf | 2010-07-11 11:06:57 +0300 | [diff] [blame^] | 13 | * Very simple screen and serial I/O |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 14 | */ |
| 15 | |
| 16 | #include "boot.h" |
| 17 | |
Pekka Enberg | fa97bdf | 2010-07-11 11:06:57 +0300 | [diff] [blame^] | 18 | #define DEFAULT_SERIAL_PORT 0x3f8 /* ttyS0 */ |
| 19 | |
| 20 | static int early_serial_base; |
| 21 | |
| 22 | #define XMTRDY 0x20 |
| 23 | |
| 24 | #define DLAB 0x80 |
| 25 | |
| 26 | #define TXR 0 /* Transmit register (WRITE) */ |
| 27 | #define RXR 0 /* Receive register (READ) */ |
| 28 | #define IER 1 /* Interrupt Enable */ |
| 29 | #define IIR 2 /* Interrupt ID */ |
| 30 | #define FCR 2 /* FIFO control */ |
| 31 | #define LCR 3 /* Line control */ |
| 32 | #define MCR 4 /* Modem control */ |
| 33 | #define LSR 5 /* Line Status */ |
| 34 | #define MSR 6 /* Modem Status */ |
| 35 | #define DLL 0 /* Divisor Latch Low */ |
| 36 | #define DLH 1 /* Divisor latch High */ |
| 37 | |
| 38 | #define DEFAULT_BAUD 9600 |
| 39 | |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 40 | /* |
| 41 | * These functions are in .inittext so they can be used to signal |
| 42 | * error during initialization. |
| 43 | */ |
| 44 | |
Pekka Enberg | fa97bdf | 2010-07-11 11:06:57 +0300 | [diff] [blame^] | 45 | static void __attribute__((section(".inittext"))) serial_putchar(int ch) |
| 46 | { |
| 47 | unsigned timeout = 0xffff; |
| 48 | |
| 49 | while ((inb(early_serial_base + LSR) & XMTRDY) == 0 && --timeout) |
| 50 | cpu_relax(); |
| 51 | |
| 52 | outb(ch, early_serial_base + TXR); |
| 53 | } |
| 54 | |
| 55 | static void __attribute__((section(".inittext"))) bios_putchar(int ch) |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 56 | { |
H. Peter Anvin | df7699c | 2009-04-01 18:13:46 -0700 | [diff] [blame] | 57 | struct biosregs ireg; |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 58 | |
H. Peter Anvin | df7699c | 2009-04-01 18:13:46 -0700 | [diff] [blame] | 59 | initregs(&ireg); |
| 60 | ireg.bx = 0x0007; |
| 61 | ireg.cx = 0x0001; |
| 62 | ireg.ah = 0x0e; |
| 63 | ireg.al = ch; |
| 64 | intcall(0x10, &ireg, NULL); |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 65 | } |
| 66 | |
Pekka Enberg | fa97bdf | 2010-07-11 11:06:57 +0300 | [diff] [blame^] | 67 | void __attribute__((section(".inittext"))) putchar(int ch) |
| 68 | { |
| 69 | if (ch == '\n') |
| 70 | putchar('\r'); /* \n -> \r\n */ |
| 71 | |
| 72 | bios_putchar(ch); |
| 73 | |
| 74 | if (early_serial_base != 0) |
| 75 | serial_putchar(ch); |
| 76 | } |
| 77 | |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 78 | void __attribute__((section(".inittext"))) puts(const char *str) |
| 79 | { |
H. Peter Anvin | df7699c | 2009-04-01 18:13:46 -0700 | [diff] [blame] | 80 | while (*str) |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 81 | putchar(*str++); |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 82 | } |
| 83 | |
| 84 | /* |
| 85 | * Read the CMOS clock through the BIOS, and return the |
| 86 | * seconds in BCD. |
| 87 | */ |
| 88 | |
| 89 | static u8 gettime(void) |
| 90 | { |
H. Peter Anvin | df7699c | 2009-04-01 18:13:46 -0700 | [diff] [blame] | 91 | struct biosregs ireg, oreg; |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 92 | |
H. Peter Anvin | df7699c | 2009-04-01 18:13:46 -0700 | [diff] [blame] | 93 | initregs(&ireg); |
| 94 | ireg.ah = 0x02; |
| 95 | intcall(0x1a, &ireg, &oreg); |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 96 | |
H. Peter Anvin | df7699c | 2009-04-01 18:13:46 -0700 | [diff] [blame] | 97 | return oreg.dh; |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 98 | } |
| 99 | |
| 100 | /* |
| 101 | * Read from the keyboard |
| 102 | */ |
| 103 | int getchar(void) |
| 104 | { |
H. Peter Anvin | df7699c | 2009-04-01 18:13:46 -0700 | [diff] [blame] | 105 | struct biosregs ireg, oreg; |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 106 | |
H. Peter Anvin | df7699c | 2009-04-01 18:13:46 -0700 | [diff] [blame] | 107 | initregs(&ireg); |
| 108 | /* ireg.ah = 0x00; */ |
| 109 | intcall(0x16, &ireg, &oreg); |
| 110 | |
| 111 | return oreg.al; |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 112 | } |
| 113 | |
| 114 | static int kbd_pending(void) |
| 115 | { |
H. Peter Anvin | df7699c | 2009-04-01 18:13:46 -0700 | [diff] [blame] | 116 | struct biosregs ireg, oreg; |
| 117 | |
| 118 | initregs(&ireg); |
| 119 | ireg.ah = 0x01; |
| 120 | intcall(0x16, &ireg, &oreg); |
| 121 | |
| 122 | return !(oreg.eflags & X86_EFLAGS_ZF); |
H. Peter Anvin | 1543610 | 2007-07-11 12:18:45 -0700 | [diff] [blame] | 123 | } |
| 124 | |
| 125 | void kbd_flush(void) |
| 126 | { |
| 127 | for (;;) { |
| 128 | if (!kbd_pending()) |
| 129 | break; |
| 130 | getchar(); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | int getchar_timeout(void) |
| 135 | { |
| 136 | int cnt = 30; |
| 137 | int t0, t1; |
| 138 | |
| 139 | t0 = gettime(); |
| 140 | |
| 141 | while (cnt) { |
| 142 | if (kbd_pending()) |
| 143 | return getchar(); |
| 144 | |
| 145 | t1 = gettime(); |
| 146 | if (t0 != t1) { |
| 147 | cnt--; |
| 148 | t0 = t1; |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | return 0; /* Timeout! */ |
| 153 | } |
Pekka Enberg | fa97bdf | 2010-07-11 11:06:57 +0300 | [diff] [blame^] | 154 | |
| 155 | static void early_serial_init(int baud) |
| 156 | { |
| 157 | unsigned char c; |
| 158 | unsigned divisor; |
| 159 | |
| 160 | outb(0x3, early_serial_base + LCR); /* 8n1 */ |
| 161 | outb(0, early_serial_base + IER); /* no interrupt */ |
| 162 | outb(0, early_serial_base + FCR); /* no fifo */ |
| 163 | outb(0x3, early_serial_base + MCR); /* DTR + RTS */ |
| 164 | |
| 165 | divisor = 115200 / baud; |
| 166 | c = inb(early_serial_base + LCR); |
| 167 | outb(c | DLAB, early_serial_base + LCR); |
| 168 | outb(divisor & 0xff, early_serial_base + DLL); |
| 169 | outb((divisor >> 8) & 0xff, early_serial_base + DLH); |
| 170 | outb(c & ~DLAB, early_serial_base + LCR); |
| 171 | } |
| 172 | |
| 173 | void console_init(void) |
| 174 | { |
| 175 | int baud = DEFAULT_BAUD; |
| 176 | char arg[32]; |
| 177 | int pos = 0; |
| 178 | |
| 179 | if (cmdline_find_option("earlyprintk", arg, sizeof arg) > 0) { |
| 180 | char *e; |
| 181 | |
| 182 | if (!strncmp(arg, "serial", 6)) { |
| 183 | early_serial_base = DEFAULT_SERIAL_PORT; |
| 184 | pos += 6; |
| 185 | } |
| 186 | |
| 187 | if (arg[pos] == ',') |
| 188 | pos++; |
| 189 | |
| 190 | if (!strncmp(arg, "ttyS", 4)) { |
| 191 | static const int bases[] = { 0x3f8, 0x2f8 }; |
| 192 | int port = 0; |
| 193 | |
| 194 | if (!strncmp(arg + pos, "ttyS", 4)) |
| 195 | pos += 4; |
| 196 | |
| 197 | if (arg[pos++] == '1') |
| 198 | port = 1; |
| 199 | |
| 200 | early_serial_base = bases[port]; |
| 201 | } |
| 202 | |
| 203 | if (arg[pos] == ',') |
| 204 | pos++; |
| 205 | |
| 206 | baud = simple_strtoull(arg + pos, &e, 0); |
| 207 | if (baud == 0 || arg + pos == e) |
| 208 | baud = DEFAULT_BAUD; |
| 209 | } |
| 210 | |
| 211 | if (early_serial_base != 0) |
| 212 | early_serial_init(baud); |
| 213 | } |