/* device-serial.c - A small serial device, until we have the real thing. Copyright (C) 2005 Free Software Foundation, Inc. Written by Marcus Brinkmann. This file is part of the GNU Hurd. The GNU Hurd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU Hurd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include "device.h" #include "output.h" /* Do not even think about improving this driver! The code here is only for testing. Go do something useful. Work on the real device driver framework. */ /* The base I/O ports for the serial device ports COM1 and COM2. */ #define UART1_BASE 0x3f8 #define UART1_IRQ 4 #define UART2_BASE 0x2f8 #define UART2_IRQ 3 /* The selected base port. */ static unsigned short int uart_base = UART2_BASE; static unsigned short int uart_irq = UART2_IRQ; /* The data register. */ #define UART_DR (uart_base + 0) /* The interrupt enable and ID registers. */ #define UART_IER (uart_base + 1) #define UART_IIR (uart_base + 2) /* The line and modem control and status registers. */ #define UART_LCR (uart_base + 3) #define UART_MCR (uart_base + 4) #define UART_LSR (uart_base + 5) #define UART_LSR_THRE (1 << 5) #define UART_MSR (uart_base + 6) /* Baudrate divisor LSB and MSB registers. */ #define UART_LSB (uart_base + 0) #define UART_MSB (uart_base + 1) /* The default speed setting. */ #define UART_SPEED_MAX 115200 #define UART_SPEED_MIN 50 #define UART_SPEED_DEFAULT UART_SPEED_MAX static void * serial_irq_handler (void *_serial) { device_t *dev = (device_t *) _serial; l4_word_t result; l4_thread_id_t irq; l4_msg_tag_t tag; unsigned int uart_speed = 2 * UART_SPEED_DEFAULT; unsigned int divider; /* Disable interrupts. */ outb (0x00, UART_IER); /* Parity bit. */ outb (0x80, UART_LCR); /* FIXME: How long do we have to wait? */ l4_sleep (l4_time_period (L4_WORD_C (100000))); /* Set baud rate. */ divider = (2 * UART_SPEED_MAX) / uart_speed; outb ((divider >> 0) & 0xff, UART_LSB); outb ((divider >> 8) & 0xff, UART_MSB); /* Set 8,N,1. */ outb (0x03, UART_LCR); /* Enable FIFOs. */ outb (0x07, UART_IIR); /* Enable RX interrupts. */ outb (0x01, UART_IER); inb (UART_IER); inb (UART_IIR); inb (UART_LCR); inb (UART_MCR); inb (UART_LSR); inb (UART_MSR); irq = l4_global_id (uart_irq, 1); result = wortel_thread_control (irq, irq, l4_nilthread, l4_myself (), (void *) -1); if (result) panic ("setting serial irq pager failed: %i", result); #define l4_reply_receive(tid) \ ({ l4_thread_id_t dummy; \ l4_ipc (tid, tid, l4_timeouts (L4_ZERO_TIME, L4_NEVER), &dummy); }) tag = l4_receive (irq); while (1) { unsigned char b; /* Check if the interrupt really came from the UART. */ b = inb (UART_IIR); if (b & 1) /* No interrupt pending. */ debug ("Spurious interrupt irq=%i", uart_irq); else { pthread_mutex_lock (&dev->serial.lock); b = inb (UART_LSR); if (b & 1) { /* receive data ready */ do { unsigned char c = inb (UART_DR); if (dev->serial.input_len == 0 && dev->serial.input_wait) { /* We have some new input. */ pthread_cond_broadcast (&dev->serial.cond); dev->serial.input_wait = 0; } if (dev->serial.input_len < SERIAL_MAX) { /* If we get too much data, we just drop some. */ dev->serial.input[dev->serial.input_len++] = c; } /* Update the status. */ b = inb (UART_LSR); } while (b & 1); } { int len = dev->serial.output_len; int tx = 0; while (tx < len && (b & (1 << 5))) { /* transmit hold ready (empty). */ unsigned char c = dev->serial.output[tx++]; outb (c, UART_DR); /* Update the status. */ b = inb (UART_LSR); } if (tx != 0) { if (len == SERIAL_MAX && dev->serial.output_wait) { /* There is now some more space. */ pthread_cond_broadcast (&dev->serial.cond); dev->serial.output_wait = 0; } len -= tx; dev->serial.output_len = len; if (len == 0) /* Disable interrupts for TX. */ outb (0x1, UART_IER); else memmove (&dev->serial.output[0], &dev->serial.output[tx], len); } } pthread_mutex_unlock (&dev->serial.lock); } l4_load_mr (0, 0); l4_reply_receive (irq); } } static void serial_init (device_t *dev) { error_t err; l4_thread_id_t irq_handler_tid; pthread_t irq_handler; l4_word_t result; irq_handler_tid = pthread_pool_get_np (); if (irq_handler_tid == l4_nilthread) panic ("Can not create the irq handler thread"); /* FIXME: We just tweak the scheduler so we can set the priority ourselves. */ result = wortel_thread_control (irq_handler_tid, irq_handler_tid, l4_myself (), l4_nilthread, (void *) -1); if (result) panic ("Can not set scheduler for serial irq handler thread: %i", result); result = l4_set_priority (irq_handler_tid, /* FIXME */ 150); if (!result) panic ("Can not set priority for serial irq handler thread: %i", result); err = pthread_create_from_l4_tid_np (&irq_handler, NULL, irq_handler_tid, serial_irq_handler, dev); if (err) panic ("Can not create the irq handler thread: %i", err); pthread_detach (irq_handler); } static error_t serial_io_read (device_t *dev, int *chr) { pthread_mutex_lock (&dev->serial.lock); while (!dev->serial.input_len) { dev->serial.input_wait = 1; pthread_cond_wait (&dev->serial.cond, &dev->serial.lock); } *chr = dev->serial.input[0]; dev->serial.input_len--; memmove (&dev->serial.input[0], &dev->serial.input[1], dev->serial.input_len); pthread_mutex_unlock (&dev->serial.lock); return 0; } static error_t serial_io_write (device_t *dev, int chr) { int old_len; pthread_mutex_lock (&dev->serial.lock); while (dev->serial.output_len == SERIAL_MAX) { dev->serial.output_wait = 1; pthread_cond_wait (&dev->serial.cond, &dev->serial.lock); } old_len = dev->serial.output_len; if (old_len == 0) { /* The output queue was empty, this means that the TX interrupts are disabled (the irq handler thread disables them after sending the last character. We have to refill the buffer and at the same time re-activate the transmit interrupts. For this, we need to enable the IRQ and write one character to get things rolling. This makes only sense if we are sending at least two characters. Another approach would be to cancel the pending IPC operation in the IRQ thread and let it reactivate the IRQ in this case. */ /* Right now, only one character is sent always. No need for IRQs. */ #if 0 if (buffer_len > 1) { memcpy (&dev->serial.output[dev->serial.output_len], &buffer[1], buffer_len - 1); /* Enable interrupts for TX. */ outb (0x3, UART_IER); /* Send first character to make sure the next TX IRQ comes soon. */ outb (buffer[0], UART_DR); } else #endif { /* Only one character, enabling IRQs makes no sense, as the next IRQ would only come after sending this first character. */ outb (chr, UART_DR); } } else { dev->serial.output[++dev->serial.output_len] = chr; } pthread_mutex_unlock (&dev->serial.lock); return 0; } /* Create a new console device in DEV. */ error_t device_serial_init (device_t *dev) { dev->io_read = serial_io_read; dev->io_write = serial_io_write; dev->serial.lock = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER; dev->serial.cond = (pthread_cond_t) PTHREAD_COND_INITIALIZER; dev->serial.input_len = 0; dev->serial.input_wait = 0; dev->serial.output_len = 0; dev->serial.output_wait = 0; /* We know this is only called once, so what the hell. */ serial_init (dev); return 0; }