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