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
45static void	comc_probe(struct console *cp);
46static int	comc_init(int arg);
47static void	comc_putchar(int c);
48static int	comc_getchar(void);
49static int	comc_getspeed(void);
50static int	comc_ischar(void);
51static int	comc_parseint(const char *string);
52static uint32_t comc_parse_pcidev(const char *string);
53static int	comc_pcidev_set(struct env_var *ev, int flags,
54		    const void *value);
55static int	comc_pcidev_handle(uint32_t locator);
56static int	comc_port_set(struct env_var *ev, int flags,
57		    const void *value);
58static void	comc_setup(int speed, int port);
59static int	comc_speed_set(struct env_var *ev, int flags,
60		    const void *value);
61
62static int	comc_curspeed;
63static int	comc_port = COMPORT;
64static uint32_t	comc_locator;
65
66struct 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
77static void
78comc_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
135static int
136comc_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
147static void
148comc_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
159static int
160comc_getchar(void)
161{
162	return (comc_ischar() ? inb(comc_port + com_data) : -1);
163}
164
165static int
166comc_ischar(void)
167{
168	return (inb(comc_port + com_lsr) & LSR_RXRDY);
169}
170
171static int
172comc_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
189static int
190comc_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 */
211static uint32_t
212comc_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
254static int
255comc_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
297static int
298comc_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
317static void
318comc_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
360static int
361comc_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
373static int
374comc_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