1/* serial.c - serial device interface */
2/*
3 *  GRUB  --  GRand Unified Bootloader
4 *  Copyright (C) 2000,2001,2002  Free Software Foundation, Inc.
5 *
6 *  This program is free software; you can redistribute it and/or modify
7 *  it under the terms of the GNU General Public License as published by
8 *  the Free Software Foundation; either version 2 of the License, or
9 *  (at your option) any later version.
10 *
11 *  This program is distributed in the hope that it will be useful,
12 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *  GNU General Public License for more details.
15 *
16 *  You should have received a copy of the GNU General Public License
17 *  along with this program; if not, write to the Free Software
18 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21#ifdef SUPPORT_SERIAL
22
23#include <shared.h>
24#include <serial.h>
25#include <term.h>
26#include <terminfo.h>
27
28/* An input buffer.  */
29static char input_buf[8];
30static int npending = 0;
31
32static int serial_x;
33static int serial_y;
34
35static int keep_track = 1;
36
37
38/* Hardware-dependent definitions.  */
39
40#ifndef GRUB_UTIL
41/* The structure for speed vs. divisor.  */
42struct divisor
43{
44  int speed;
45  unsigned short div;
46};
47
48/* Store the port number of a serial unit.  */
49static unsigned short serial_hw_port = 0;
50
51/* The table which lists common configurations.  */
52static struct divisor divisor_tab[] =
53  {
54    { 2400,   0x0030 },
55    { 4800,   0x0018 },
56    { 9600,   0x000C },
57    { 19200,  0x0006 },
58    { 38400,  0x0003 },
59    { 57600,  0x0002 },
60    { 115200, 0x0001 }
61  };
62
63/* Read a byte from a port.  */
64static inline unsigned char
65inb (unsigned short port)
66{
67  unsigned char value;
68
69  asm volatile ("inb	%w1, %0" : "=a" (value) : "Nd" (port));
70  asm volatile ("outb	%%al, $0x80" : : );
71
72  return value;
73}
74
75/* Write a byte to a port.  */
76static inline void
77outb (unsigned short port, unsigned char value)
78{
79  asm volatile ("outb	%b0, %w1" : : "a" (value), "Nd" (port));
80  asm volatile ("outb	%%al, $0x80" : : );
81}
82
83/* Fetch a key.  */
84int
85serial_hw_fetch (void)
86{
87  if (inb (serial_hw_port + UART_LSR) & UART_DATA_READY)
88    return inb (serial_hw_port + UART_RX);
89
90  return -1;
91}
92
93/* Put a chararacter.  */
94void
95serial_hw_put (int c)
96{
97  int timeout = 100000;
98
99  /* Wait until the transmitter holding register is empty.  */
100  while ((inb (serial_hw_port + UART_LSR) & UART_EMPTY_TRANSMITTER) == 0)
101    {
102      if (--timeout == 0)
103	/* There is something wrong. But what can I do?  */
104	return;
105    }
106
107  outb (serial_hw_port + UART_TX, c);
108}
109
110void
111serial_hw_delay (void)
112{
113  outb (0x80, 0);
114}
115
116/* Return the port number for the UNITth serial device.  */
117unsigned short
118serial_hw_get_port (int unit)
119{
120  /* The BIOS data area.  */
121  const unsigned short *addr = (const unsigned short *) 0x0400;
122
123  return addr[unit];
124}
125
126/* Initialize a serial device. PORT is the port number for a serial device.
127   SPEED is a DTE-DTE speed which must be one of these: 2400, 4800, 9600,
128   19200, 38400, 57600 and 115200. WORD_LEN is the word length to be used
129   for the device. Likewise, PARITY is the type of the parity and
130   STOP_BIT_LEN is the length of the stop bit. The possible values for
131   WORD_LEN, PARITY and STOP_BIT_LEN are defined in the header file as
132   macros.  */
133int
134serial_hw_init (unsigned short port, unsigned int speed,
135		int word_len, int parity, int stop_bit_len)
136{
137  int i;
138  unsigned short div = 0;
139  unsigned char status = 0;
140
141  /* Turn off the interrupt.  */
142  outb (port + UART_IER, 0);
143
144  /* Set DLAB.  */
145  outb (port + UART_LCR, UART_DLAB);
146
147  /* Set the baud rate.  */
148  for (i = 0; i < sizeof (divisor_tab) / sizeof (divisor_tab[0]); i++)
149    if (divisor_tab[i].speed == speed)
150      {
151	div = divisor_tab[i].div;
152	break;
153      }
154
155  if (div == 0)
156    return 0;
157
158  outb (port + UART_DLL, div & 0xFF);
159  outb (port + UART_DLH, div >> 8);
160
161  /* Set the line status.  */
162  status |= parity | word_len | stop_bit_len;
163  outb (port + UART_LCR, status);
164
165  /* Enable the FIFO.  */
166  outb (port + UART_FCR, UART_ENABLE_FIFO);
167
168  /* Turn on DTR, RTS, and OUT2.  */
169  outb (port + UART_MCR, UART_ENABLE_MODEM);
170
171  /* Store the port number.  */
172  serial_hw_port = port;
173
174  /* Drain the input buffer.  */
175  while (serial_checkkey () != -1)
176    (void) serial_getkey ();
177
178  /* Get rid of TERM_NEED_INIT from the serial terminal.  */
179  for (i = 0; term_table[i].name; i++)
180    if (grub_strcmp (term_table[i].name, "serial") == 0)
181      {
182	term_table[i].flags &= ~TERM_NEED_INIT;
183	break;
184      }
185
186  /* FIXME: should check if the serial terminal was found.  */
187
188  return 1;
189}
190#endif /* ! GRUB_UTIL */
191
192
193/* Generic definitions.  */
194
195static void
196serial_translate_key_sequence (void)
197{
198  const struct
199  {
200    char key;
201    char ascii;
202  }
203  three_code_table[] =
204    {
205      {'A', 16},
206      {'B', 14},
207      {'C', 6},
208      {'D', 2},
209      {'F', 5},
210      {'H', 1},
211      {'4', 4}
212    };
213
214  const struct
215  {
216    short key;
217    char ascii;
218  }
219  four_code_table[] =
220    {
221      {('1' | ('~' << 8)), 1},
222      {('3' | ('~' << 8)), 4},
223      {('5' | ('~' << 8)), 7},
224      {('6' | ('~' << 8)), 3},
225    };
226
227  /* The buffer must start with ``ESC [''.  */
228  if (*((unsigned short *) input_buf) != ('\e' | ('[' << 8)))
229    return;
230
231  if (npending >= 3)
232    {
233      int i;
234
235      for (i = 0;
236	   i < sizeof (three_code_table) / sizeof (three_code_table[0]);
237	   i++)
238	if (three_code_table[i].key == input_buf[2])
239	  {
240	    input_buf[0] = three_code_table[i].ascii;
241	    npending -= 2;
242	    grub_memmove (input_buf + 1, input_buf + 3, npending - 1);
243	    return;
244	  }
245    }
246
247  if (npending >= 4)
248    {
249      int i;
250      short key = *((short *) (input_buf + 2));
251
252      for (i = 0;
253	   i < sizeof (four_code_table) / sizeof (four_code_table[0]);
254	   i++)
255	if (four_code_table[i].key == key)
256	  {
257	    input_buf[0] = four_code_table[i].ascii;
258	    npending -= 3;
259	    grub_memmove (input_buf + 1, input_buf + 4, npending - 1);
260	    return;
261	  }
262    }
263}
264
265static
266int fill_input_buf (int nowait)
267{
268  int i;
269
270  for (i = 0; i < 10000 && npending < sizeof (input_buf); i++)
271    {
272      int c;
273
274      c = serial_hw_fetch ();
275      if (c >= 0)
276	{
277	  input_buf[npending++] = c;
278
279	  /* Reset the counter to zero, to wait for the same interval.  */
280	  i = 0;
281	}
282
283      if (nowait)
284	break;
285    }
286
287  /* Translate some key sequences.  */
288  serial_translate_key_sequence ();
289
290  return npending;
291}
292
293/* The serial version of getkey.  */
294int
295serial_getkey (void)
296{
297  int c;
298
299  while (! fill_input_buf (0))
300    ;
301
302  c = input_buf[0];
303  npending--;
304  grub_memmove (input_buf, input_buf + 1, npending);
305
306  return c;
307}
308
309/* The serial version of checkkey.  */
310int
311serial_checkkey (void)
312{
313  if (fill_input_buf (1))
314    return input_buf[0];
315
316  return -1;
317}
318
319/* The serial version of grub_putchar.  */
320void
321serial_putchar (int c)
322{
323  /* Keep track of the cursor.  */
324  if (keep_track)
325    {
326      /* The serial terminal doesn't have VGA fonts.  */
327      switch (c)
328	{
329	case DISP_UL:
330	  c = ACS_ULCORNER;
331	  break;
332	case DISP_UR:
333	  c = ACS_URCORNER;
334	  break;
335	case DISP_LL:
336	  c = ACS_LLCORNER;
337	  break;
338	case DISP_LR:
339	  c = ACS_LRCORNER;
340	  break;
341	case DISP_HORIZ:
342	  c = ACS_HLINE;
343	  break;
344	case DISP_VERT:
345	  c = ACS_VLINE;
346	  break;
347	case DISP_LEFT:
348	  c = ACS_LARROW;
349	  break;
350	case DISP_RIGHT:
351	  c = ACS_RARROW;
352	  break;
353	case DISP_UP:
354	  c = ACS_UARROW;
355	  break;
356	case DISP_DOWN:
357	  c = ACS_DARROW;
358	  break;
359	default:
360	  break;
361	}
362
363      switch (c)
364	{
365	case '\r':
366	  serial_x = 0;
367	  break;
368
369	case '\n':
370	  serial_y++;
371	  break;
372
373	case '\b':
374	case 127:
375	  if (serial_x > 0)
376	    serial_x--;
377	  break;
378
379	case '\a':
380	  break;
381
382	default:
383	  if (serial_x >= 79)
384	    {
385	      serial_putchar ('\r');
386	      serial_putchar ('\n');
387	    }
388	  serial_x++;
389	  break;
390	}
391    }
392
393  serial_hw_put (c);
394}
395
396int
397serial_getxy (void)
398{
399  return (serial_x << 8) | serial_y;
400}
401
402void
403serial_gotoxy (int x, int y)
404{
405  keep_track = 0;
406  ti_cursor_address (x, y);
407  keep_track = 1;
408
409  serial_x = x;
410  serial_y = y;
411}
412
413void
414serial_cls (void)
415{
416  keep_track = 0;
417  ti_clear_screen ();
418  keep_track = 1;
419
420  serial_x = serial_y = 0;
421}
422
423void
424serial_setcolorstate (color_state state)
425{
426  keep_track = 0;
427  if (state == COLOR_STATE_HIGHLIGHT)
428    ti_enter_standout_mode ();
429  else
430    ti_exit_standout_mode ();
431  keep_track = 1;
432}
433
434#endif /* SUPPORT_SERIAL */
435