~ilya-yanok/ubuntu/precise/grub2/fix-for-948716

« back to all changes in this revision

Viewing changes to term/i386/pc/serial.c

  • Committer: Bazaar Package Importer
  • Author(s): Otavio Salvador
  • Date: 2006-01-05 15:20:40 UTC
  • mto: (17.3.1 squeeze) (1.9.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 4.
  • Revision ID: james.westby@ubuntu.com-20060105152040-b72i5pq1a82z22yi
Tags: upstream-1.92
ImportĀ upstreamĀ versionĀ 1.92

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *  GRUB  --  GRand Unified Bootloader
 
3
 *  Copyright (C) 2000,2001,2002,2003,2004,2005  Free Software Foundation, Inc.
 
4
 *
 
5
 *  This program is free software; you can redistribute it and/or modify
 
6
 *  it under the terms of the GNU General Public License as published by
 
7
 *  the Free Software Foundation; either version 2 of the License, or
 
8
 *  (at your option) any later version.
 
9
 *
 
10
 *  This program is distributed in the hope that it will be useful,
 
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
 *  GNU General Public License for more details.
 
14
 *
 
15
 *  You should have received a copy of the GNU General Public License
 
16
 *  along with this program; if not, write to the Free Software
 
17
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
18
 */
 
19
 
 
20
#include <grub/machine/serial.h>
 
21
#include <grub/machine/console.h>
 
22
#include <grub/term.h>
 
23
#include <grub/types.h>
 
24
#include <grub/dl.h>
 
25
#include <grub/misc.h>
 
26
#include <grub/normal.h>
 
27
#include <grub/arg.h>
 
28
#include <grub/terminfo.h>
 
29
 
 
30
#define TEXT_WIDTH      80
 
31
#define TEXT_HEIGHT     25
 
32
 
 
33
static unsigned int xpos, ypos;
 
34
static unsigned int keep_track = 1;
 
35
static unsigned int registered = 0;
 
36
 
 
37
/* An input buffer.  */
 
38
static char input_buf[8];
 
39
static unsigned int npending = 0;
 
40
 
 
41
/* Argument options.  */
 
42
static const struct grub_arg_option options[] =
 
43
{
 
44
  {"unit",   'u', 0, "Set the serial unit",             0, ARG_TYPE_INT},
 
45
  {"port",   'p', 0, "Set the serial port address",     0, ARG_TYPE_STRING},
 
46
  {"speed",  's', 0, "Set the serial port speed",       0, ARG_TYPE_INT},
 
47
  {"word",   'w', 0, "Set the serial port word length", 0, ARG_TYPE_INT},
 
48
  {"parity", 'r', 0, "Set the serial port parity",      0, ARG_TYPE_STRING},
 
49
  {"stop",   't', 0, "Set the serial port stop bits",   0, ARG_TYPE_INT},
 
50
  {0, 0, 0, 0, 0, 0}
 
51
};
 
52
 
 
53
/* Serial port settings.  */
 
54
struct serial_port
 
55
{
 
56
  unsigned short port;
 
57
  unsigned short divisor;
 
58
  unsigned short word_len;
 
59
  unsigned int   parity;
 
60
  unsigned short stop_bits;
 
61
};
 
62
 
 
63
/* Serial port settings.  */
 
64
static struct serial_port serial_settings;
 
65
 
 
66
/* Read a byte from a port.  */
 
67
static inline unsigned char
 
68
inb (const unsigned short port)
 
69
{
 
70
  unsigned char value;
 
71
 
 
72
  asm volatile ("inb    %w1, %0" : "=a" (value) : "Nd" (port));
 
73
  asm volatile ("outb   %%al, $0x80" : : );
 
74
 
 
75
  return value;
 
76
}
 
77
 
 
78
/* Write a byte to a port.  */
 
79
static inline void
 
80
outb (const unsigned short port, const unsigned char value)
 
81
{
 
82
  asm volatile ("outb   %b0, %w1" : : "a" (value), "Nd" (port));
 
83
  asm volatile ("outb   %%al, $0x80" : : );
 
84
}
 
85
 
 
86
/* Return the port number for the UNITth serial device.  */
 
87
static inline unsigned short
 
88
serial_hw_get_port (const unsigned short unit)
 
89
{
 
90
  /* The BIOS data area.  */
 
91
  const unsigned short *addr = (const unsigned short *) 0x0400;
 
92
  return addr[unit];
 
93
}
 
94
 
 
95
/* Fetch a key.  */
 
96
static int
 
97
serial_hw_fetch (void)
 
98
{
 
99
  if (inb (serial_settings.port + UART_LSR) & UART_DATA_READY)
 
100
    return inb (serial_settings.port + UART_RX);
 
101
 
 
102
  return -1;
 
103
}
 
104
 
 
105
/* Put a chararacter.  */
 
106
static void
 
107
serial_hw_put (const int c)
 
108
{
 
109
  unsigned int timeout = 100000;
 
110
 
 
111
  /* Wait until the transmitter holding register is empty.  */
 
112
  while ((inb (serial_settings.port + UART_LSR) & UART_EMPTY_TRANSMITTER) == 0)
 
113
    {
 
114
      if (--timeout == 0)
 
115
        /* There is something wrong. But what can I do?  */
 
116
        return;
 
117
    }
 
118
 
 
119
  outb (serial_settings.port + UART_TX, c);
 
120
}
 
121
 
 
122
static void
 
123
serial_translate_key_sequence (void)
 
124
{
 
125
  static struct
 
126
  {
 
127
    char key;
 
128
    char ascii;
 
129
  }
 
130
  three_code_table[] =
 
131
    {
 
132
      {'A', 16},
 
133
      {'B', 14},
 
134
      {'C', 6},
 
135
      {'D', 2},
 
136
      {'F', 5},
 
137
      {'H', 1},
 
138
      {'4', 4}
 
139
    };
 
140
  
 
141
  static struct
 
142
  {
 
143
      short key;
 
144
      char ascii;
 
145
  }
 
146
  four_code_table[] =
 
147
    {
 
148
      {('1' | ('~' << 8)), 1},
 
149
      {('3' | ('~' << 8)), 4},
 
150
      {('5' | ('~' << 8)), 7},
 
151
      {('6' | ('~' << 8)), 3}
 
152
    };
 
153
 
 
154
  /* The buffer must start with "ESC [".  */
 
155
  if (*((unsigned short *) input_buf) != ('\e' | ('[' << 8)))
 
156
    return;
 
157
 
 
158
  if (npending >= 3)
 
159
    {
 
160
      unsigned int i;
 
161
      
 
162
      for (i = 0;
 
163
           i < sizeof (three_code_table) / sizeof (three_code_table[0]);
 
164
           i++)
 
165
        if (three_code_table[i].key == input_buf[2])
 
166
          {
 
167
            input_buf[0] = three_code_table[i].ascii;
 
168
            npending -= 2;
 
169
            grub_memmove (input_buf + 1, input_buf + 3, npending - 1);
 
170
            return;
 
171
          }
 
172
    }
 
173
 
 
174
  if (npending >= 4)
 
175
    {
 
176
      unsigned int i;
 
177
      short key = *((short *) (input_buf + 2));
 
178
      
 
179
      for (i = 0;
 
180
           i < sizeof (four_code_table) / sizeof (four_code_table[0]);
 
181
           i++)
 
182
        if (four_code_table[i].key == key)
 
183
          {
 
184
            input_buf[0] = four_code_table[i].ascii;
 
185
            npending -= 3;
 
186
            grub_memmove (input_buf + 1, input_buf + 4, npending - 1);
 
187
            return;
 
188
          }
 
189
    }
 
190
}
 
191
 
 
192
static int
 
193
fill_input_buf (const int nowait)
 
194
{
 
195
  int i;
 
196
 
 
197
  for (i = 0; i < 10000 && npending < sizeof (input_buf); i++)
 
198
    {
 
199
      int c;
 
200
      
 
201
      c = serial_hw_fetch ();
 
202
      if (c >= 0)
 
203
        {
 
204
          input_buf[npending++] = c;
 
205
          
 
206
          /* Reset the counter to zero, to wait for the same interval.  */
 
207
          i = 0;
 
208
        }
 
209
      
 
210
      if (nowait)
 
211
        break;
 
212
    }
 
213
 
 
214
  /* Translate some key sequences.  */
 
215
  serial_translate_key_sequence ();
 
216
 
 
217
  return npending;
 
218
}
 
219
 
 
220
/* Convert speed to divisor.  */
 
221
static unsigned short
 
222
serial_get_divisor (unsigned int speed)
 
223
{
 
224
  unsigned int i;
 
225
 
 
226
  /* The structure for speed vs. divisor.  */
 
227
  struct divisor
 
228
  {
 
229
    unsigned int speed;
 
230
    unsigned short div;
 
231
  };
 
232
 
 
233
  /* The table which lists common configurations.  */
 
234
  /* 1843200 / (speed * 16)  */
 
235
  static struct divisor divisor_tab[] =
 
236
    {
 
237
      { 2400,   0x0030 },
 
238
      { 4800,   0x0018 },
 
239
      { 9600,   0x000C },
 
240
      { 19200,  0x0006 },
 
241
      { 38400,  0x0003 },
 
242
      { 57600,  0x0002 },
 
243
      { 115200, 0x0001 }
 
244
    };
 
245
 
 
246
  /* Set the baud rate.  */
 
247
  for (i = 0; i < sizeof (divisor_tab) / sizeof (divisor_tab[0]); i++)
 
248
    if (divisor_tab[i].speed == speed)
 
249
      return divisor_tab[i].div;
 
250
  return 0;
 
251
}
 
252
 
 
253
/* The serial version of checkkey.  */
 
254
static int
 
255
grub_serial_checkkey (void)
 
256
{
 
257
  if (fill_input_buf (1))
 
258
    return input_buf[0];
 
259
  else
 
260
    return -1;
 
261
}
 
262
 
 
263
/* The serial version of getkey.  */
 
264
static int
 
265
grub_serial_getkey (void)
 
266
{
 
267
  int c;
 
268
 
 
269
  while (! fill_input_buf (0))
 
270
    ;
 
271
 
 
272
  c = input_buf[0];
 
273
  grub_memmove (input_buf, input_buf + 1, --npending);
 
274
 
 
275
  return c;
 
276
}
 
277
 
 
278
/* Initialize a serial device. PORT is the port number for a serial device.
 
279
   SPEED is a DTE-DTE speed which must be one of these: 2400, 4800, 9600,
 
280
   19200, 38400, 57600 and 115200. WORD_LEN is the word length to be used
 
281
   for the device. Likewise, PARITY is the type of the parity and
 
282
   STOP_BIT_LEN is the length of the stop bit. The possible values for
 
283
   WORD_LEN, PARITY and STOP_BIT_LEN are defined in the header file as
 
284
   macros.  */
 
285
static grub_err_t
 
286
serial_hw_init (void)
 
287
{
 
288
  unsigned char status = 0;
 
289
 
 
290
  /* Turn off the interupt.  */
 
291
  outb (serial_settings.port + UART_IER, 0);
 
292
 
 
293
  /* Set DLAB.  */
 
294
  outb (serial_settings.port + UART_LCR, UART_DLAB);
 
295
 
 
296
  /* Set the baud rate.  */
 
297
  outb (serial_settings.port + UART_DLL, serial_settings.divisor & 0xFF);
 
298
  outb (serial_settings.port + UART_DLH, serial_settings.divisor >> 8 );
 
299
 
 
300
  /* Set the line status.  */
 
301
  status |= (serial_settings.parity
 
302
             | serial_settings.word_len
 
303
             | serial_settings.stop_bits);
 
304
  outb (serial_settings.port + UART_LCR, status);
 
305
 
 
306
  /* Enable the FIFO.  */
 
307
  outb (serial_settings.port + UART_FCR, UART_ENABLE_FIFO);
 
308
 
 
309
  /* Turn on DTR, RTS, and OUT2.  */
 
310
  outb (serial_settings.port + UART_MCR, UART_ENABLE_MODEM);
 
311
 
 
312
  /* Drain the input buffer.  */
 
313
  while (grub_serial_checkkey () != -1)
 
314
    (void) grub_serial_getkey ();
 
315
 
 
316
  /*  FIXME: should check if the serial terminal was found.  */
 
317
 
 
318
  return GRUB_ERR_NONE;
 
319
}
 
320
 
 
321
/* The serial version of putchar.  */
 
322
static void
 
323
grub_serial_putchar (grub_uint32_t c)
 
324
{
 
325
  /* Keep track of the cursor.  */
 
326
  if (keep_track)
 
327
    {
 
328
      /* The serial terminal does not have VGA fonts.  */
 
329
      if (c > 0x7F)
 
330
        {
 
331
          /* Better than nothing.  */
 
332
          switch (c)
 
333
            {
 
334
            case GRUB_TERM_DISP_LEFT:
 
335
              c = '<';
 
336
              break;
 
337
              
 
338
            case GRUB_TERM_DISP_UP:
 
339
              c = '^';
 
340
              break;
 
341
              
 
342
            case GRUB_TERM_DISP_RIGHT:
 
343
              c = '>';
 
344
              break;
 
345
              
 
346
            case GRUB_TERM_DISP_DOWN:
 
347
              c = 'v';
 
348
              break;
 
349
              
 
350
            case GRUB_TERM_DISP_HLINE:
 
351
              c = '-';
 
352
              break;
 
353
              
 
354
            case GRUB_TERM_DISP_VLINE:
 
355
              c = '|';
 
356
              break;
 
357
              
 
358
            case GRUB_TERM_DISP_UL:
 
359
            case GRUB_TERM_DISP_UR:
 
360
            case GRUB_TERM_DISP_LL:
 
361
            case GRUB_TERM_DISP_LR:
 
362
              c = '+';
 
363
              break;
 
364
              
 
365
            default:
 
366
              c = '?';
 
367
              break;
 
368
            }
 
369
        }
 
370
      
 
371
      switch (c)
 
372
        {
 
373
        case '\a':
 
374
          break;
 
375
          
 
376
        case '\b':
 
377
        case 127:
 
378
          if (xpos > 0)
 
379
            xpos--;
 
380
          break;
 
381
          
 
382
        case '\n':
 
383
          if (ypos < TEXT_HEIGHT)
 
384
            ypos++;
 
385
          break;
 
386
          
 
387
        case '\r':
 
388
          xpos = 0;
 
389
          break;
 
390
          
 
391
        default:
 
392
          if (xpos >= TEXT_WIDTH)
 
393
            {
 
394
              grub_putchar ('\r');
 
395
              grub_putchar ('\n');
 
396
            }
 
397
          xpos++;
 
398
          break;
 
399
        }
 
400
    }
 
401
  
 
402
  serial_hw_put (c);
 
403
}
 
404
 
 
405
static grub_ssize_t
 
406
grub_serial_getcharwidth (grub_uint32_t c __attribute__ ((unused)))
 
407
{
 
408
  return 1;
 
409
}
 
410
 
 
411
static grub_uint16_t
 
412
grub_serial_getwh (void)
 
413
{
 
414
  return (TEXT_WIDTH << 8) | TEXT_HEIGHT;
 
415
}
 
416
 
 
417
static grub_uint16_t
 
418
grub_serial_getxy (void)
 
419
{
 
420
  return ((xpos << 8) | ypos);
 
421
}
 
422
 
 
423
static void
 
424
grub_serial_gotoxy (grub_uint8_t x, grub_uint8_t y)
 
425
{
 
426
  if (x > TEXT_WIDTH || y > TEXT_HEIGHT)
 
427
    {
 
428
      grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid point (%u,%u)", x, y);
 
429
    }
 
430
  else
 
431
    {
 
432
      keep_track = 0;
 
433
      grub_terminfo_gotoxy (x, y);
 
434
      keep_track = 1;
 
435
      
 
436
      xpos = x;
 
437
      ypos = y;
 
438
    }
 
439
}
 
440
 
 
441
static void
 
442
grub_serial_cls (void)
 
443
{
 
444
  keep_track = 0;
 
445
  grub_terminfo_cls ();
 
446
  keep_track = 1;
 
447
 
 
448
  xpos = ypos = 0;
 
449
}
 
450
 
 
451
static void
 
452
grub_serial_setcolorstate (const grub_term_color_state state)
 
453
{
 
454
  keep_track = 0;
 
455
  switch (state)
 
456
    {
 
457
    case GRUB_TERM_COLOR_STANDARD:
 
458
    case GRUB_TERM_COLOR_NORMAL:
 
459
      grub_terminfo_reverse_video_off ();
 
460
      break;
 
461
    case GRUB_TERM_COLOR_HIGHLIGHT:
 
462
      grub_terminfo_reverse_video_on ();
 
463
      break;
 
464
    default:
 
465
      break;
 
466
    }
 
467
  keep_track = 1;
 
468
}
 
469
 
 
470
static void
 
471
grub_serial_setcolor (grub_uint8_t normal_color __attribute__ ((unused)),
 
472
                      grub_uint8_t highlight_color __attribute__ ((unused)))
 
473
{
 
474
  /* FIXME */
 
475
}
 
476
 
 
477
static void
 
478
grub_serial_setcursor (const int on)
 
479
{
 
480
  if (on)
 
481
    grub_terminfo_cursor_on ();
 
482
  else
 
483
    grub_terminfo_cursor_off ();
 
484
}
 
485
 
 
486
static struct grub_term grub_serial_term =
 
487
{
 
488
  .name = "serial",
 
489
  .init = 0,
 
490
  .fini = 0,
 
491
  .putchar = grub_serial_putchar,
 
492
  .getcharwidth = grub_serial_getcharwidth,
 
493
  .checkkey = grub_serial_checkkey,
 
494
  .getkey = grub_serial_getkey,
 
495
  .getwh = grub_serial_getwh,
 
496
  .getxy = grub_serial_getxy,
 
497
  .gotoxy = grub_serial_gotoxy,
 
498
  .cls = grub_serial_cls,
 
499
  .setcolorstate = grub_serial_setcolorstate,
 
500
  .setcolor = grub_serial_setcolor,
 
501
  .setcursor = grub_serial_setcursor,
 
502
  .flags = 0,
 
503
  .next = 0
 
504
};
 
505
 
 
506
 
 
507
 
 
508
static grub_err_t
 
509
grub_cmd_serial (struct grub_arg_list *state,
 
510
                 int argc __attribute__ ((unused)),
 
511
                 char **args __attribute__ ((unused)))
 
512
{
 
513
  struct serial_port backup_settings = serial_settings;
 
514
  grub_err_t hwiniterr;
 
515
  int arg;
 
516
 
 
517
  if (state[0].set)
 
518
    {
 
519
      arg = grub_strtoul (state[0].arg, 0, 0);
 
520
      if (arg >= 0 && arg < 4)
 
521
        serial_settings.port = serial_hw_get_port ((int) arg);
 
522
      else
 
523
        return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad unit number.");
 
524
    }
 
525
  
 
526
  if (state[1].set)
 
527
    serial_settings.port = (unsigned short) grub_strtoul (state[1].arg, 0, 0);
 
528
  
 
529
  if (state[2].set)
 
530
    {
 
531
      unsigned long speed;
 
532
 
 
533
      speed = grub_strtoul (state[2].arg, 0, 0);
 
534
      serial_settings.divisor = serial_get_divisor ((unsigned int) speed);
 
535
      if (serial_settings.divisor == 0)
 
536
        {
 
537
          serial_settings = backup_settings;
 
538
          return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad speed");
 
539
        }
 
540
    }
 
541
  
 
542
  if (state[3].set)
 
543
    {
 
544
      if (! grub_strcmp (state[3].arg, "5"))
 
545
        serial_settings.word_len = UART_5BITS_WORD;
 
546
      else if (! grub_strcmp (state[3].arg, "6"))
 
547
        serial_settings.word_len = UART_6BITS_WORD;
 
548
      else if (! grub_strcmp (state[3].arg, "7"))
 
549
        serial_settings.word_len = UART_7BITS_WORD;
 
550
      else if (! grub_strcmp (state[3].arg, "8"))
 
551
        serial_settings.word_len = UART_8BITS_WORD;
 
552
      else
 
553
        {
 
554
          serial_settings = backup_settings;
 
555
          return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad word length");
 
556
        }
 
557
    }
 
558
  
 
559
  if (state[4].set)
 
560
    {
 
561
      if (! grub_strcmp (state[4].arg, "no"))
 
562
        serial_settings.parity = UART_NO_PARITY;
 
563
      else if (! grub_strcmp (state[4].arg, "odd"))
 
564
        serial_settings.parity = UART_ODD_PARITY;
 
565
      else if (! grub_strcmp (state[4].arg, "even"))
 
566
        serial_settings.parity = UART_EVEN_PARITY;
 
567
      else
 
568
        {
 
569
          serial_settings = backup_settings;
 
570
          return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad parity");
 
571
        }
 
572
    }
 
573
  
 
574
  if (state[5].set)
 
575
    {
 
576
      if (! grub_strcmp (state[5].arg, "1"))
 
577
        serial_settings.stop_bits = UART_1_STOP_BIT;
 
578
      else if (! grub_strcmp (state[5].arg, "2"))
 
579
        serial_settings.stop_bits = UART_2_STOP_BITS;
 
580
      else
 
581
        {
 
582
          serial_settings = backup_settings;
 
583
          return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad number of stop bits");
 
584
        }
 
585
    }
 
586
 
 
587
  /* Initialize with new settings.  */
 
588
  hwiniterr = serial_hw_init ();
 
589
  
 
590
  if (hwiniterr == GRUB_ERR_NONE)
 
591
    {
 
592
      /* Register terminal if not yet registered.  */
 
593
      if (registered == 0)
 
594
        {
 
595
          grub_term_register (&grub_serial_term);
 
596
          registered = 1;
 
597
        }
 
598
    }
 
599
  else
 
600
    {
 
601
      /* Initialization with new settings failed.  */
 
602
      if (registered == 1)
 
603
        {
 
604
          /* If the terminal is registered, attempt to restore previous
 
605
             settings.  */
 
606
          serial_settings = backup_settings;
 
607
          if (serial_hw_init () != GRUB_ERR_NONE)
 
608
            {
 
609
              /* If unable to restore settings, unregister terminal.  */
 
610
              grub_term_unregister (&grub_serial_term);
 
611
              registered = 0;
 
612
            }
 
613
        }
 
614
    }
 
615
  
 
616
  return hwiniterr;
 
617
}
 
618
 
 
619
GRUB_MOD_INIT(serial)
 
620
{
 
621
  (void) mod;                   /* To stop warning. */
 
622
  grub_register_command ("serial", grub_cmd_serial, GRUB_COMMAND_FLAG_BOTH,
 
623
                         "serial [OPTIONS...]", "Configure serial port.", options);
 
624
  /* Set default settings.  */
 
625
  serial_settings.port      = serial_hw_get_port (0);
 
626
  serial_settings.divisor   = serial_get_divisor (9600);
 
627
  serial_settings.word_len  = UART_8BITS_WORD;
 
628
  serial_settings.parity    = UART_NO_PARITY;
 
629
  serial_settings.stop_bits = UART_1_STOP_BIT;
 
630
}
 
631
 
 
632
GRUB_MOD_FINI(serial)
 
633
{
 
634
  grub_unregister_command ("serial");
 
635
  if (registered == 1)          /* Unregister terminal only if registered. */
 
636
    grub_term_unregister (&grub_serial_term);
 
637
}