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