comconsole.c revision 329070
1139776Simp/*-
216468Sdyson * Copyright (c) 1998 Michael Smith (msmith@freebsd.org)
316468Sdyson *
416468Sdyson * Redistribution and use in source and binary forms, with or without
516468Sdyson * modification, are permitted provided that the following conditions
616468Sdyson * are met:
716468Sdyson * 1. Redistributions of source code must retain the above copyright
816468Sdyson *    notice, this list of conditions and the following disclaimer.
916468Sdyson * 2. Redistributions in binary form must reproduce the above copyright
1016468Sdyson *    notice, this list of conditions and the following disclaimer in the
1116468Sdyson *    documentation and/or other materials provided with the distribution.
1216468Sdyson *
1316468Sdyson * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1416468Sdyson * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1516468Sdyson * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1616468Sdyson * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1716468Sdyson * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1816468Sdyson * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
1916468Sdyson * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2016468Sdyson * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2116468Sdyson * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2216468Sdyson * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2316468Sdyson * SUCH DAMAGE.
2416468Sdyson */
2516468Sdyson
2616468Sdyson#include <sys/cdefs.h>
2716468Sdyson__FBSDID("$FreeBSD: stable/11/sys/boot/i386/libi386/comconsole.c 329070 2018-02-09 17:25:34Z kevans $");
2816468Sdyson
2916468Sdyson#include <stand.h>
3016468Sdyson#include <bootstrap.h>
3116468Sdyson#include <machine/cpufunc.h>
3216468Sdyson#include <dev/ic/ns16550.h>
3316468Sdyson#include <dev/pci/pcireg.h>
3416468Sdyson#include "libi386.h"
3550477Speter
3616468Sdyson#define COMC_FMT	0x3		/* 8N1 */
3716468Sdyson#define COMC_TXWAIT	0x40000		/* transmit timeout */
38147692Speter#define COMC_BPS(x)	(115200 / (x))	/* speed to DLAB divisor */
39147692Speter#define COMC_DIV2BPS(x)	(115200 / (x))	/* DLAB divisor to speed */
4016468Sdyson
4116468Sdyson#ifndef	COMPORT
4276166Smarkm#define COMPORT		0x3f8
43120583Srwatson#endif
44120583Srwatson#ifndef	COMSPEED
45168968Salc#define COMSPEED	9600
4616468Sdyson#endif
47194766Skib
48248084Sattiliostatic void	comc_probe(struct console *cp);
49183600Skibstatic int	comc_init(int arg);
50205014Snwhitehornstatic void	comc_putchar(int c);
51186562Skibstatic int	comc_getchar(void);
52186562Skibstatic int	comc_getspeed(void);
5387321Sdesstatic int	comc_ischar(void);
54120583Srwatsonstatic int	comc_parseint(const char *string);
5576166Smarkmstatic uint32_t comc_parse_pcidev(const char *string);
5687321Sdesstatic int	comc_pcidev_set(struct env_var *ev, int flags,
5777031Sru		    const void *value);
5816468Sdysonstatic int	comc_pcidev_handle(uint32_t locator);
5916468Sdysonstatic int	comc_port_set(struct env_var *ev, int flags,
60185984Skib		    const void *value);
6116468Sdysonstatic void	comc_setup(int speed, int port);
6216468Sdysonstatic int	comc_speed_set(struct env_var *ev, int flags,
6316468Sdyson		    const void *value);
6416468Sdyson
6516468Sdysonstatic int	comc_curspeed;
6619260Sdysonstatic int	comc_port = COMPORT;
6716468Sdysonstatic uint32_t	comc_locator;
6819260Sdyson
6919260Sdysonstruct console comconsole = {
7019260Sdyson    "comconsole",
7119260Sdyson    "serial port",
7219260Sdyson    0,
7319260Sdyson    comc_probe,
7419260Sdyson    comc_init,
7519260Sdyson    comc_putchar,
7619260Sdyson    comc_getchar,
7719260Sdyson    comc_ischar
7816468Sdyson};
7987321Sdes
8016468Sdysonstatic void
81185984Skibcomc_probe(struct console *cp)
82185984Skib{
83185765Skib    char intbuf[16];
84168968Salc    char *cons, *env;
85120583Srwatson    int speed, port;
86216128Strasz    uint32_t locator;
87241896Skib
88185765Skib    if (comc_curspeed == 0) {
89205014Snwhitehorn	comc_curspeed = COMSPEED;
90147692Speter	/*
91147692Speter	 * Assume that the speed was set by an earlier boot loader if
9216468Sdyson	 * comconsole is already the preferred console.
93118907Srwatson	 */
94118907Srwatson	cons = getenv("console");
95118907Srwatson	if ((cons != NULL && strcmp(cons, comconsole.c_name) == 0) ||
96118907Srwatson	    getenv("boot_multicons") != NULL) {
97118907Srwatson		comc_curspeed = comc_getspeed();
98118907Srwatson	}
9916468Sdyson
10016468Sdyson	env = getenv("comconsole_speed");
10116468Sdyson	if (env != NULL) {
102205014Snwhitehorn	    speed = comc_parseint(env);
103217896Sdchagin	    if (speed > 0)
104217896Sdchagin		comc_curspeed = speed;
105147692Speter	}
106147692Speter
107147692Speter	sprintf(intbuf, "%d", comc_curspeed);
108147692Speter	unsetenv("comconsole_speed");
109168763Sdes	env_setenv("comconsole_speed", EV_VOLATILE, intbuf, comc_speed_set,
110185984Skib	    env_nounset);
111185984Skib
112185984Skib	env = getenv("comconsole_port");
113185984Skib	if (env != NULL) {
114168968Salc	    port = comc_parseint(env);
115183600Skib	    if (port > 0)
116183600Skib		comc_port = port;
11716468Sdyson	}
11842957Sdillon
119185765Skib	sprintf(intbuf, "%d", comc_port);
12016468Sdyson	unsetenv("comconsole_port");
12116468Sdyson	env_setenv("comconsole_port", EV_VOLATILE, intbuf, comc_port_set,
122185765Skib	    env_nounset);
123185765Skib
12416468Sdyson	env = getenv("comconsole_pcidev");
12543748Sdillon	if (env != NULL) {
12616468Sdyson	    locator = comc_parse_pcidev(env);
12716468Sdyson	    if (locator != 0)
128185765Skib		    comc_pcidev_handle(locator);
129185765Skib	}
130185765Skib
131185765Skib	unsetenv("comconsole_pcidev");
132168968Salc	env_setenv("comconsole_pcidev", EV_VOLATILE, env, comc_pcidev_set,
13316468Sdyson	    env_nounset);
134168968Salc    }
135251423Salc    comc_setup(comc_curspeed, comc_port);
136168968Salc}
137168968Salc
138168968Salcstatic int
139216128Straszcomc_init(int arg)
14016468Sdyson{
14116468Sdyson
14216468Sdyson    comc_setup(comc_curspeed, comc_port);
14316468Sdyson
144168968Salc    if ((comconsole.c_flags & (C_PRESENTIN | C_PRESENTOUT)) ==
14516468Sdyson	(C_PRESENTIN | C_PRESENTOUT))
14616468Sdyson	return (CMD_OK);
14716468Sdyson    return (CMD_ERROR);
14816468Sdyson}
149168968Salc
150168968Salcstatic void
151251423Salccomc_putchar(int c)
152168968Salc{
153251423Salc    int wait;
15416468Sdyson
155168968Salc    for (wait = COMC_TXWAIT; wait > 0; wait--)
156185765Skib        if (inb(comc_port + com_lsr) & LSR_TXRDY) {
157185765Skib	    outb(comc_port + com_data, (u_char)c);
15816468Sdyson	    break;
159120583Srwatson	}
160120583Srwatson}
16135497Sdyson
162185765Skibstatic int
16387321Sdescomc_getchar(void)
16487321Sdes{
16535497Sdyson    return (comc_ischar() ? inb(comc_port + com_data) : -1);
166168968Salc}
16735497Sdyson
16887321Sdesstatic int
16935497Sdysoncomc_ischar(void)
170168968Salc{
171168968Salc    return (inb(comc_port + com_lsr) & LSR_RXRDY);
17235497Sdyson}
17387321Sdes
17435497Sdysonstatic int
175168968Salccomc_speed_set(struct env_var *ev, int flags, const void *value)
17635497Sdyson{
177195840Sjhb    int speed;
17887321Sdes
17935497Sdyson    if (value == NULL || (speed = comc_parseint(value)) <= 0) {
180168968Salc	printf("Invalid speed\n");
18135497Sdyson	return (CMD_ERROR);
18235497Sdyson    }
183168968Salc
184251423Salc    if (comc_curspeed != speed)
185123247Sdes	comc_setup(speed, comc_port);
18635497Sdyson
18735497Sdyson    env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
18835497Sdyson
189251423Salc    return (CMD_OK);
190168968Salc}
191184652Sjhb
192184652Sjhbstatic int
193168968Salccomc_port_set(struct env_var *ev, int flags, const void *value)
19417303Sdyson{
19517303Sdyson    int port;
19635497Sdyson
19735497Sdyson    if (value == NULL || (port = comc_parseint(value)) <= 0) {
19835497Sdyson	printf("Invalid port\n");
19916468Sdyson	return (CMD_ERROR);
20016468Sdyson    }
20116468Sdyson
20216468Sdyson    if (comc_port != port)
203194766Skib	comc_setup(comc_curspeed, port);
204194766Skib
20516468Sdyson    env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
206183600Skib
207194766Skib    return (CMD_OK);
208185765Skib}
209147692Speter
210205014Snwhitehorn/*
211147692Speter * Input: bus:dev:func[:bar]. If bar is not specified, it is 0x10.
212147692Speter * Output: bar[24:16] bus[15:8] dev[7:3] func[2:0]
213147692Speter */
214147692Speterstatic uint32_t
215185765Skibcomc_parse_pcidev(const char *string)
216185765Skib{
217185765Skib#ifdef NO_PCI
21835497Sdyson	return (0);
219185765Skib#else
220185765Skib	char *p, *p1;
221194766Skib	uint8_t bus, dev, func, bar;
222216128Strasz	uint32_t locator;
22316468Sdyson	int pres;
224120583Srwatson
225120583Srwatson	pres = strtol(string, &p, 0);
226185864Skib	if (p == string || *p != ':' || pres < 0 )
227183600Skib		return (0);
228183600Skib	bus = pres;
22919260Sdyson	p1 = ++p;
23016468Sdyson
231186563Skib	pres = strtol(p1, &p, 0);
232185765Skib	if (p == string || *p != ':' || pres < 0 )
233185765Skib		return (0);
234185765Skib	dev = pres;
235185765Skib	p1 = ++p;
236185765Skib
237185765Skib	pres = strtol(p1, &p, 0);
238185765Skib	if (p == string || (*p != ':' && *p != '\0') || pres < 0 )
239185765Skib		return (0);
24016468Sdyson	func = pres;
241168968Salc
242185984Skib	if (*p == ':') {
24387321Sdes		p1 = ++p;
24416468Sdyson		pres = strtol(p1, &p, 0);
245		if (p == string || *p != '\0' || pres <= 0 )
246			return (0);
247		bar = pres;
248	} else
249		bar = 0x10;
250
251	locator = (bar << 16) | biospci_locator(bus, dev, func);
252	return (locator);
253#endif
254}
255
256static int
257comc_pcidev_handle(uint32_t locator)
258{
259#ifdef NO_PCI
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, 2, &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
298static int
299comc_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
318static void
319comc_setup(int speed, int port)
320{
321    static int TRY_COUNT = 1000000;
322    char intbuf[64];
323    int tries;
324
325    unsetenv("hw.uart.console");
326    comc_curspeed = speed;
327    comc_port = port;
328    if ((comconsole.c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) == 0)
329	return;
330
331    outb(comc_port + com_cfcr, CFCR_DLAB | COMC_FMT);
332    outb(comc_port + com_dlbl, COMC_BPS(speed) & 0xff);
333    outb(comc_port + com_dlbh, COMC_BPS(speed) >> 8);
334    outb(comc_port + com_cfcr, COMC_FMT);
335    outb(comc_port + com_mcr, MCR_RTS | MCR_DTR);
336
337    tries = 0;
338    do
339        inb(comc_port + com_data);
340    while (inb(comc_port + com_lsr) & LSR_RXRDY && ++tries < TRY_COUNT);
341
342    if (tries < TRY_COUNT) {
343	comconsole.c_flags |= (C_PRESENTIN | C_PRESENTOUT);
344	sprintf(intbuf, "io:%d,br:%d", comc_port, comc_curspeed);
345	env_setenv("hw.uart.console", EV_VOLATILE, intbuf, NULL, NULL);
346    } else
347	comconsole.c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
348}
349
350static int
351comc_parseint(const char *speedstr)
352{
353    char *p;
354    int speed;
355
356    speed = strtol(speedstr, &p, 0);
357    if (p == speedstr || *p != '\0' || speed <= 0)
358	return (-1);
359
360    return (speed);
361}
362
363static int
364comc_getspeed(void)
365{
366	u_int	divisor;
367	u_char	dlbh;
368	u_char	dlbl;
369	u_char	cfcr;
370
371	cfcr = inb(comc_port + com_cfcr);
372	outb(comc_port + com_cfcr, CFCR_DLAB | cfcr);
373
374	dlbl = inb(comc_port + com_dlbl);
375	dlbh = inb(comc_port + com_dlbh);
376
377	outb(comc_port + com_cfcr, cfcr);
378
379	divisor = dlbh << 8 | dlbl;
380
381	/* XXX there should be more sanity checking. */
382	if (divisor == 0)
383		return (COMSPEED);
384	return (COMC_DIV2BPS(divisor));
385}
386