1 /*- 2 * Copyright (c) 1998 Michael Smith (msmith@freebsd.org) 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 * SUCH DAMAGE. 24 */ 25 26 #include <sys/cdefs.h> 27 __FBSDID("$FreeBSD$"); 28 29 #include <stand.h> 30 #include <bootstrap.h> 31 #include <machine/cpufunc.h> 32 #include <dev/ic/ns16550.h> 33 #include <dev/pci/pcireg.h> 34 #include "libi386.h" 35 36 #define COMC_FMT 0x3 /* 8N1 */ 37 #define COMC_TXWAIT 0x40000 /* transmit timeout */ 38 #define COMC_BPS(x) (115200 / (x)) /* speed to DLAB divisor */ 39 #define COMC_DIV2BPS(x) (115200 / (x)) /* DLAB divisor to speed */ 40 41 #ifndef COMPORT 42 #define COMPORT 0x3f8 43 #endif 44 #ifndef COMSPEED 45 #define COMSPEED 9600 46 #endif 47 48 static void comc_probe(struct console *cp); 49 static int comc_init(int arg); 50 static void comc_putchar(int c); 51 static int comc_getchar(void); 52 static int comc_getspeed(void); 53 static int comc_ischar(void); 54 static int comc_parseint(const char *string); 55 static uint32_t comc_parse_pcidev(const char *string); 56 static int comc_pcidev_set(struct env_var *ev, int flags, 57 const void *value); 58 static int comc_pcidev_handle(uint32_t locator); 59 static int comc_port_set(struct env_var *ev, int flags, 60 const void *value); 61 static void comc_setup(int speed, int port); 62 static int comc_speed_set(struct env_var *ev, int flags, 63 const void *value); 64 65 static int comc_curspeed; 66 static int comc_port = COMPORT; 67 static uint32_t comc_locator; 68 69 struct console comconsole = { 70 .c_name = "comconsole", 71 .c_desc = "serial port", 72 .c_flags = 0, 73 .c_probe = comc_probe, 74 .c_init = comc_init, 75 .c_out = comc_putchar, 76 .c_in = comc_getchar, 77 .c_ready = comc_ischar 78 }; 79 80 static void 81 comc_probe(struct console *cp) 82 { 83 char intbuf[16]; 84 char *cons, *env; 85 int speed, port; 86 uint32_t locator; 87 88 #if defined(__amd64__) 89 extern bool efi_comconsole_avail; 90 91 if (efi_comconsole_avail) { 92 /* 93 * If EFI provides serial I/O, then don't use this legacy 94 * com driver to avoid conflicts with the firmware's driver. 95 * Change c_name so that it cannot be found in the lookup. 96 */ 97 comconsole.c_name = "xcomconsole"; 98 return; 99 } 100 #endif 101 102 if (comc_curspeed == 0) { 103 comc_curspeed = COMSPEED; 104 /* 105 * Assume that the speed was set by an earlier boot loader if 106 * comconsole is already the preferred console. 107 */ 108 cons = getenv("console"); 109 if ((cons != NULL && strcmp(cons, comconsole.c_name) == 0) || 110 getenv("boot_multicons") != NULL) { 111 comc_curspeed = comc_getspeed(); 112 } 113 114 env = getenv("comconsole_speed"); 115 if (env != NULL) { 116 speed = comc_parseint(env); 117 if (speed > 0) 118 comc_curspeed = speed; 119 } 120 121 sprintf(intbuf, "%d", comc_curspeed); 122 unsetenv("comconsole_speed"); 123 env_setenv("comconsole_speed", EV_VOLATILE, intbuf, 124 comc_speed_set, env_nounset); 125 126 env = getenv("comconsole_port"); 127 if (env != NULL) { 128 port = comc_parseint(env); 129 if (port > 0) 130 comc_port = port; 131 } 132 133 sprintf(intbuf, "%d", comc_port); 134 unsetenv("comconsole_port"); 135 env_setenv("comconsole_port", EV_VOLATILE, intbuf, 136 comc_port_set, env_nounset); 137 138 env = getenv("comconsole_pcidev"); 139 if (env != NULL) { 140 locator = comc_parse_pcidev(env); 141 if (locator != 0) 142 comc_pcidev_handle(locator); 143 } 144 145 unsetenv("comconsole_pcidev"); 146 env_setenv("comconsole_pcidev", EV_VOLATILE, env, 147 comc_pcidev_set, env_nounset); 148 } 149 comc_setup(comc_curspeed, comc_port); 150 } 151 152 static int 153 comc_init(int arg) 154 { 155 156 comc_setup(comc_curspeed, comc_port); 157 158 if ((comconsole.c_flags & (C_PRESENTIN | C_PRESENTOUT)) == 159 (C_PRESENTIN | C_PRESENTOUT)) 160 return (CMD_OK); 161 return (CMD_ERROR); 162 } 163 164 static void 165 comc_putchar(int c) 166 { 167 int wait; 168 169 for (wait = COMC_TXWAIT; wait > 0; wait--) 170 if (inb(comc_port + com_lsr) & LSR_TXRDY) { 171 outb(comc_port + com_data, (u_char)c); 172 break; 173 } 174 } 175 176 static int 177 comc_getchar(void) 178 { 179 return (comc_ischar() ? inb(comc_port + com_data) : -1); 180 } 181 182 static int 183 comc_ischar(void) 184 { 185 return (inb(comc_port + com_lsr) & LSR_RXRDY); 186 } 187 188 static int 189 comc_speed_set(struct env_var *ev, int flags, const void *value) 190 { 191 int speed; 192 193 if (value == NULL || (speed = comc_parseint(value)) <= 0) { 194 printf("Invalid speed\n"); 195 return (CMD_ERROR); 196 } 197 198 if (comc_curspeed != speed) 199 comc_setup(speed, comc_port); 200 201 env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); 202 203 return (CMD_OK); 204 } 205 206 static int 207 comc_port_set(struct env_var *ev, int flags, const void *value) 208 { 209 int port; 210 211 if (value == NULL || (port = comc_parseint(value)) <= 0) { 212 printf("Invalid port\n"); 213 return (CMD_ERROR); 214 } 215 216 if (comc_port != port) 217 comc_setup(comc_curspeed, port); 218 219 env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); 220 221 return (CMD_OK); 222 } 223 224 /* 225 * Input: bus:dev:func[:bar]. If bar is not specified, it is 0x10. 226 * Output: bar[24:16] bus[15:8] dev[7:3] func[2:0] 227 */ 228 static uint32_t 229 comc_parse_pcidev(const char *string) 230 { 231 #ifdef EFI 232 /* We don't support PCI in EFI yet */ 233 return (0); 234 #else 235 char *p, *p1; 236 uint8_t bus, dev, func, bar; 237 uint32_t locator; 238 int pres; 239 240 pres = strtol(string, &p, 0); 241 if (p == string || *p != ':' || pres < 0 ) 242 return (0); 243 bus = pres; 244 p1 = ++p; 245 246 pres = strtol(p1, &p, 0); 247 if (p == string || *p != ':' || pres < 0 ) 248 return (0); 249 dev = pres; 250 p1 = ++p; 251 252 pres = strtol(p1, &p, 0); 253 if (p == string || (*p != ':' && *p != '\0') || pres < 0 ) 254 return (0); 255 func = pres; 256 257 if (*p == ':') { 258 p1 = ++p; 259 pres = strtol(p1, &p, 0); 260 if (p == string || *p != '\0' || pres <= 0 ) 261 return (0); 262 bar = pres; 263 } else 264 bar = 0x10; 265 266 locator = (bar << 16) | biospci_locator(bus, dev, func); 267 return (locator); 268 #endif 269 } 270 271 static int 272 comc_pcidev_handle(uint32_t locator) 273 { 274 #ifdef EFI 275 /* We don't support PCI in EFI yet */ 276 return (CMD_ERROR); 277 #else 278 char intbuf[64]; 279 uint32_t port; 280 281 if (biospci_read_config(locator & 0xffff, 282 (locator & 0xff0000) >> 16, BIOSPCI_32BITS, &port) == -1) { 283 printf("Cannot read bar at 0x%x\n", locator); 284 return (CMD_ERROR); 285 } 286 287 /* 288 * biospci_read_config() sets port == 0xffffffff if the pcidev 289 * isn't found on the bus. Check for 0xffffffff and return to not 290 * panic in BTX. 291 */ 292 if (port == 0xffffffff) { 293 printf("Cannot find specified pcidev\n"); 294 return (CMD_ERROR); 295 } 296 if (!PCI_BAR_IO(port)) { 297 printf("Memory bar at 0x%x\n", locator); 298 return (CMD_ERROR); 299 } 300 port &= PCIM_BAR_IO_BASE; 301 302 sprintf(intbuf, "%d", port); 303 unsetenv("comconsole_port"); 304 env_setenv("comconsole_port", EV_VOLATILE, intbuf, 305 comc_port_set, env_nounset); 306 307 comc_setup(comc_curspeed, port); 308 comc_locator = locator; 309 310 return (CMD_OK); 311 #endif 312 } 313 314 static int 315 comc_pcidev_set(struct env_var *ev, int flags, const void *value) 316 { 317 uint32_t locator; 318 int error; 319 320 if (value == NULL || (locator = comc_parse_pcidev(value)) <= 0) { 321 printf("Invalid pcidev\n"); 322 return (CMD_ERROR); 323 } 324 if ((comconsole.c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) != 0 && 325 comc_locator != locator) { 326 error = comc_pcidev_handle(locator); 327 if (error != CMD_OK) 328 return (error); 329 } 330 env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); 331 return (CMD_OK); 332 } 333 334 static void 335 comc_setup(int speed, int port) 336 { 337 static int TRY_COUNT = 1000000; 338 char intbuf[64]; 339 int tries; 340 341 comc_curspeed = speed; 342 comc_port = port; 343 if ((comconsole.c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) == 0) 344 return; 345 346 unsetenv("hw.uart.console"); 347 348 #define COMC_TEST 0xbb 349 /* 350 * Write byte to scratch register and read it out. 351 */ 352 outb(comc_port + com_scr, COMC_TEST); 353 if (inb(comc_port + com_scr) != COMC_TEST) { 354 comconsole.c_flags &= ~(C_PRESENTIN | C_PRESENTOUT); 355 return; 356 } 357 358 outb(comc_port + com_cfcr, CFCR_DLAB | COMC_FMT); 359 outb(comc_port + com_dlbl, COMC_BPS(speed) & 0xff); 360 outb(comc_port + com_dlbh, COMC_BPS(speed) >> 8); 361 outb(comc_port + com_cfcr, COMC_FMT); 362 outb(comc_port + com_mcr, MCR_RTS | MCR_DTR); 363 364 tries = 0; 365 do 366 inb(comc_port + com_data); 367 while (inb(comc_port + com_lsr) & LSR_RXRDY && ++tries < TRY_COUNT); 368 369 if (tries < TRY_COUNT) { 370 comconsole.c_flags |= (C_PRESENTIN | C_PRESENTOUT); 371 sprintf(intbuf, "io:%d,br:%d", comc_port, comc_curspeed); 372 env_setenv("hw.uart.console", EV_VOLATILE, intbuf, NULL, NULL); 373 } else 374 comconsole.c_flags &= ~(C_PRESENTIN | C_PRESENTOUT); 375 } 376 377 static int 378 comc_parseint(const char *speedstr) 379 { 380 char *p; 381 int speed; 382 383 speed = strtol(speedstr, &p, 0); 384 if (p == speedstr || *p != '\0' || speed <= 0) 385 return (-1); 386 387 return (speed); 388 } 389 390 static int 391 comc_getspeed(void) 392 { 393 u_int divisor; 394 u_char dlbh; 395 u_char dlbl; 396 u_char cfcr; 397 398 cfcr = inb(comc_port + com_cfcr); 399 outb(comc_port + com_cfcr, CFCR_DLAB | cfcr); 400 401 dlbl = inb(comc_port + com_dlbl); 402 dlbh = inb(comc_port + com_dlbh); 403 404 outb(comc_port + com_cfcr, cfcr); 405 406 divisor = dlbh << 8 | dlbl; 407 408 /* XXX there should be more sanity checking. */ 409 if (divisor == 0) 410 return (COMSPEED); 411 return (COMC_DIV2BPS(divisor)); 412 } 413