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 (comc_curspeed == 0) { 89 comc_curspeed = COMSPEED; 90 /* 91 * Assume that the speed was set by an earlier boot loader if 92 * comconsole is already the preferred console. 93 */ 94 cons = getenv("console"); 95 if ((cons != NULL && strcmp(cons, comconsole.c_name) == 0) || 96 getenv("boot_multicons") != NULL) { 97 comc_curspeed = comc_getspeed(); 98 } 99 100 env = getenv("comconsole_speed"); 101 if (env != NULL) { 102 speed = comc_parseint(env); 103 if (speed > 0) 104 comc_curspeed = speed; 105 } 106 107 sprintf(intbuf, "%d", comc_curspeed); 108 unsetenv("comconsole_speed"); 109 env_setenv("comconsole_speed", EV_VOLATILE, intbuf, 110 comc_speed_set, env_nounset); 111 112 env = getenv("comconsole_port"); 113 if (env != NULL) { 114 port = comc_parseint(env); 115 if (port > 0) 116 comc_port = port; 117 } 118 119 sprintf(intbuf, "%d", comc_port); 120 unsetenv("comconsole_port"); 121 env_setenv("comconsole_port", EV_VOLATILE, intbuf, 122 comc_port_set, env_nounset); 123 124 env = getenv("comconsole_pcidev"); 125 if (env != NULL) { 126 locator = comc_parse_pcidev(env); 127 if (locator != 0) 128 comc_pcidev_handle(locator); 129 } 130 131 unsetenv("comconsole_pcidev"); 132 env_setenv("comconsole_pcidev", EV_VOLATILE, env, 133 comc_pcidev_set, env_nounset); 134 } 135 comc_setup(comc_curspeed, comc_port); 136 } 137 138 static int 139 comc_init(int arg) 140 { 141 142 comc_setup(comc_curspeed, comc_port); 143 144 if ((comconsole.c_flags & (C_PRESENTIN | C_PRESENTOUT)) == 145 (C_PRESENTIN | C_PRESENTOUT)) 146 return (CMD_OK); 147 return (CMD_ERROR); 148 } 149 150 static void 151 comc_putchar(int c) 152 { 153 int wait; 154 155 for (wait = COMC_TXWAIT; wait > 0; wait--) 156 if (inb(comc_port + com_lsr) & LSR_TXRDY) { 157 outb(comc_port + com_data, (u_char)c); 158 break; 159 } 160 } 161 162 static int 163 comc_getchar(void) 164 { 165 return (comc_ischar() ? inb(comc_port + com_data) : -1); 166 } 167 168 static int 169 comc_ischar(void) 170 { 171 return (inb(comc_port + com_lsr) & LSR_RXRDY); 172 } 173 174 static int 175 comc_speed_set(struct env_var *ev, int flags, const void *value) 176 { 177 int speed; 178 179 if (value == NULL || (speed = comc_parseint(value)) <= 0) { 180 printf("Invalid speed\n"); 181 return (CMD_ERROR); 182 } 183 184 if (comc_curspeed != speed) 185 comc_setup(speed, comc_port); 186 187 env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); 188 189 return (CMD_OK); 190 } 191 192 static int 193 comc_port_set(struct env_var *ev, int flags, const void *value) 194 { 195 int port; 196 197 if (value == NULL || (port = comc_parseint(value)) <= 0) { 198 printf("Invalid port\n"); 199 return (CMD_ERROR); 200 } 201 202 if (comc_port != port) 203 comc_setup(comc_curspeed, port); 204 205 env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); 206 207 return (CMD_OK); 208 } 209 210 /* 211 * Input: bus:dev:func[:bar]. If bar is not specified, it is 0x10. 212 * Output: bar[24:16] bus[15:8] dev[7:3] func[2:0] 213 */ 214 static uint32_t 215 comc_parse_pcidev(const char *string) 216 { 217 #ifdef EFI 218 /* We don't support PCI in EFI yet */ 219 return (0); 220 #else 221 char *p, *p1; 222 uint8_t bus, dev, func, bar; 223 uint32_t locator; 224 int pres; 225 226 pres = strtol(string, &p, 0); 227 if (p == string || *p != ':' || pres < 0 ) 228 return (0); 229 bus = pres; 230 p1 = ++p; 231 232 pres = strtol(p1, &p, 0); 233 if (p == string || *p != ':' || pres < 0 ) 234 return (0); 235 dev = pres; 236 p1 = ++p; 237 238 pres = strtol(p1, &p, 0); 239 if (p == string || (*p != ':' && *p != '\0') || pres < 0 ) 240 return (0); 241 func = pres; 242 243 if (*p == ':') { 244 p1 = ++p; 245 pres = strtol(p1, &p, 0); 246 if (p == string || *p != '\0' || pres <= 0 ) 247 return (0); 248 bar = pres; 249 } else 250 bar = 0x10; 251 252 locator = (bar << 16) | biospci_locator(bus, dev, func); 253 return (locator); 254 #endif 255 } 256 257 static int 258 comc_pcidev_handle(uint32_t locator) 259 { 260 #ifdef EFI 261 /* We don't support PCI in EFI yet */ 262 return (CMD_ERROR); 263 #else 264 char intbuf[64]; 265 uint32_t port; 266 267 if (biospci_read_config(locator & 0xffff, 268 (locator & 0xff0000) >> 16, BIOSPCI_32BITS, &port) == -1) { 269 printf("Cannot read bar at 0x%x\n", locator); 270 return (CMD_ERROR); 271 } 272 273 /* 274 * biospci_read_config() sets port == 0xffffffff if the pcidev 275 * isn't found on the bus. Check for 0xffffffff and return to not 276 * panic in BTX. 277 */ 278 if (port == 0xffffffff) { 279 printf("Cannot find specified pcidev\n"); 280 return (CMD_ERROR); 281 } 282 if (!PCI_BAR_IO(port)) { 283 printf("Memory bar at 0x%x\n", locator); 284 return (CMD_ERROR); 285 } 286 port &= PCIM_BAR_IO_BASE; 287 288 sprintf(intbuf, "%d", port); 289 unsetenv("comconsole_port"); 290 env_setenv("comconsole_port", EV_VOLATILE, intbuf, 291 comc_port_set, env_nounset); 292 293 comc_setup(comc_curspeed, port); 294 comc_locator = locator; 295 296 return (CMD_OK); 297 #endif 298 } 299 300 static int 301 comc_pcidev_set(struct env_var *ev, int flags, const void *value) 302 { 303 uint32_t locator; 304 int error; 305 306 if (value == NULL || (locator = comc_parse_pcidev(value)) <= 0) { 307 printf("Invalid pcidev\n"); 308 return (CMD_ERROR); 309 } 310 if ((comconsole.c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) != 0 && 311 comc_locator != locator) { 312 error = comc_pcidev_handle(locator); 313 if (error != CMD_OK) 314 return (error); 315 } 316 env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); 317 return (CMD_OK); 318 } 319 320 static void 321 comc_setup(int speed, int port) 322 { 323 static int TRY_COUNT = 1000000; 324 char intbuf[64]; 325 int tries; 326 327 comc_curspeed = speed; 328 comc_port = port; 329 if ((comconsole.c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) == 0) 330 return; 331 332 unsetenv("hw.uart.console"); 333 334 #define COMC_TEST 0xbb 335 /* 336 * Write byte to scratch register and read it out. 337 */ 338 outb(comc_port + com_scr, COMC_TEST); 339 if (inb(comc_port + com_scr) != COMC_TEST) { 340 comconsole.c_flags &= ~(C_PRESENTIN | C_PRESENTOUT); 341 return; 342 } 343 344 outb(comc_port + com_cfcr, CFCR_DLAB | COMC_FMT); 345 outb(comc_port + com_dlbl, COMC_BPS(speed) & 0xff); 346 outb(comc_port + com_dlbh, COMC_BPS(speed) >> 8); 347 outb(comc_port + com_cfcr, COMC_FMT); 348 outb(comc_port + com_mcr, MCR_RTS | MCR_DTR); 349 350 tries = 0; 351 do 352 inb(comc_port + com_data); 353 while (inb(comc_port + com_lsr) & LSR_RXRDY && ++tries < TRY_COUNT); 354 355 if (tries < TRY_COUNT) { 356 comconsole.c_flags |= (C_PRESENTIN | C_PRESENTOUT); 357 sprintf(intbuf, "io:%d,br:%d", comc_port, comc_curspeed); 358 env_setenv("hw.uart.console", EV_VOLATILE, intbuf, NULL, NULL); 359 } else 360 comconsole.c_flags &= ~(C_PRESENTIN | C_PRESENTOUT); 361 } 362 363 static int 364 comc_parseint(const char *speedstr) 365 { 366 char *p; 367 int speed; 368 369 speed = strtol(speedstr, &p, 0); 370 if (p == speedstr || *p != '\0' || speed <= 0) 371 return (-1); 372 373 return (speed); 374 } 375 376 static int 377 comc_getspeed(void) 378 { 379 u_int divisor; 380 u_char dlbh; 381 u_char dlbl; 382 u_char cfcr; 383 384 cfcr = inb(comc_port + com_cfcr); 385 outb(comc_port + com_cfcr, CFCR_DLAB | cfcr); 386 387 dlbl = inb(comc_port + com_dlbl); 388 dlbh = inb(comc_port + com_dlbh); 389 390 outb(comc_port + com_cfcr, cfcr); 391 392 divisor = dlbh << 8 | dlbl; 393 394 /* XXX there should be more sanity checking. */ 395 if (divisor == 0) 396 return (COMSPEED); 397 return (COMC_DIV2BPS(divisor)); 398 } 399