/* * Copyright (C) 2005-2007 Jan Kiszka . * * Xenomai is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Xenomai 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 * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Xenomai; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include MODULE_DESCRIPTION("RTDM-based driver for 16550A UARTs"); MODULE_AUTHOR("Jan Kiszka "); MODULE_VERSION("1.5.2"); MODULE_LICENSE("GPL"); #define RT_16550_DRIVER_NAME "xeno_16550A" #define MAX_DEVICES 8 #define IN_BUFFER_SIZE 4096 #define OUT_BUFFER_SIZE 4096 #define DEFAULT_BAUD_BASE 115200 #define DEFAULT_TX_FIFO 16 #define PARITY_MASK 0x03 #define DATA_BITS_MASK 0x03 #define STOP_BITS_MASK 0x01 #define FIFO_MASK 0xC0 #define EVENT_MASK 0x0F #define LCR_DLAB 0x80 #define FCR_FIFO 0x01 #define FCR_RESET_RX 0x02 #define FCR_RESET_TX 0x04 #define IER_RX 0x01 #define IER_TX 0x02 #define IER_STAT 0x04 #define IER_MODEM 0x08 #define IIR_MODEM 0x00 #define IIR_PIRQ 0x01 #define IIR_TX 0x02 #define IIR_RX 0x04 #define IIR_STAT 0x06 #define IIR_MASK 0x07 #define RHR 0 /* Receive Holding Buffer */ #define THR 0 /* Transmit Holding Buffer */ #define DLL 0 /* Divisor Latch LSB */ #define IER 1 /* Interrupt Enable Register */ #define DLM 1 /* Divisor Latch MSB */ #define IIR 2 /* Interrupt Id Register */ #define FCR 2 /* Fifo Control Register */ #define LCR 3 /* Line Control Register */ #define MCR 4 /* Modem Control Register */ #define LSR 5 /* Line Status Register */ #define MSR 6 /* Modem Status Register */ struct rt_16550_context { struct rtser_config config; /* current device configuration */ rtdm_irq_t irq_handle; /* device IRQ handle */ rtdm_lock_t lock; /* lock to protect context struct */ unsigned long base_addr; /* hardware IO base address */ #ifdef CONFIG_XENO_DRIVERS_16550A_ANY int io_mode; /* hardware IO-access mode */ #endif int tx_fifo; /* cached global tx_fifo[] */ int in_head; /* RX ring buffer, head pointer */ int in_tail; /* RX ring buffer, tail pointer */ size_t in_npend; /* pending bytes in RX ring */ int in_nwait; /* bytes the user waits for */ rtdm_event_t in_event; /* raised to unblock reader */ char in_buf[IN_BUFFER_SIZE]; /* RX ring buffer */ volatile unsigned long in_lock; /* single-reader lock */ uint64_t *in_history; /* RX timestamp buffer */ int out_head; /* TX ring buffer, head pointer */ int out_tail; /* TX ring buffer, tail pointer */ size_t out_npend; /* pending bytes in TX ring */ rtdm_event_t out_event; /* raised to unblock writer */ char out_buf[OUT_BUFFER_SIZE]; /* TX ring buffer */ rtdm_mutex_t out_lock; /* single-writer mutex */ uint64_t last_timestamp; /* timestamp of last event */ int ioc_events; /* recorded events */ rtdm_event_t ioc_event; /* raised to unblock event waiter */ volatile unsigned long ioc_event_lock; /* single-waiter lock */ int ier_status; /* IER cache */ int mcr_status; /* MCR cache */ int status; /* cache for LSR + soft-states */ int saved_errors; /* error cache for RTIOC_GET_STATUS */ }; static const struct rtser_config default_config = { 0xFFFF, RTSER_DEF_BAUD, RTSER_DEF_PARITY, RTSER_DEF_BITS, RTSER_DEF_STOPB, RTSER_DEF_HAND, RTSER_DEF_FIFO_DEPTH, 0, RTSER_DEF_TIMEOUT, RTSER_DEF_TIMEOUT, RTSER_DEF_TIMEOUT, RTSER_DEF_TIMESTAMP_HISTORY, RTSER_DEF_EVENT_MASK, RTSER_DEF_RS485 }; static struct rtdm_device *device[MAX_DEVICES]; static unsigned int irq[MAX_DEVICES]; static unsigned long irqtype[MAX_DEVICES] = { [0 ... MAX_DEVICES-1] = RTDM_IRQTYPE_SHARED | RTDM_IRQTYPE_EDGE }; static unsigned int baud_base[MAX_DEVICES]; static int tx_fifo[MAX_DEVICES]; module_param_array(irq, uint, NULL, 0400); module_param_array(baud_base, uint, NULL, 0400); module_param_array(tx_fifo, int, NULL, 0400); MODULE_PARM_DESC(irq, "IRQ numbers of the serial devices"); MODULE_PARM_DESC(baud_base, "Maximum baud rate of the serial device " "(internal clock rate / 16)"); MODULE_PARM_DESC(tx_fifo, "Transmitter FIFO size"); #include "16550A_io.h" #include "16550A_pnp.h" #include "16550A_pci.h" static inline int rt_16550_rx_interrupt(struct rt_16550_context *ctx, uint64_t * timestamp) { unsigned long base = ctx->base_addr; int mode = rt_16550_io_mode_from_ctx(ctx); int rbytes = 0; int lsr = 0; int c; do { c = rt_16550_reg_in(mode, base, RHR); /* read input char */ ctx->in_buf[ctx->in_tail] = c; if (ctx->in_history) ctx->in_history[ctx->in_tail] = *timestamp; ctx->in_tail = (ctx->in_tail + 1) & (IN_BUFFER_SIZE - 1); if (++ctx->in_npend > IN_BUFFER_SIZE) { lsr |= RTSER_SOFT_OVERRUN_ERR; ctx->in_npend--; } rbytes++; lsr &= ~RTSER_LSR_DATA; lsr |= (rt_16550_reg_in(mode, base, LSR) & (RTSER_LSR_DATA | RTSER_LSR_OVERRUN_ERR | RTSER_LSR_PARITY_ERR | RTSER_LSR_FRAMING_ERR | RTSER_LSR_BREAK_IND)); } while (lsr & RTSER_LSR_DATA); /* save new errors */ ctx->status |= lsr; /* If we are enforcing the RTSCTS control flow and the input buffer is busy above the specified high watermark, clear RTS. */ /* if (uart->i_count >= uart->config.rts_hiwm && (uart->config.handshake & RT_UART_RTSCTS) != 0 && (uart->modem & MCR_RTS) != 0) { uart->modem &= ~MCR_RTS; rt_16550_reg_out(mode, base, MCR, uart->modem); }*/ return rbytes; } static void rt_16550_tx_fill(struct rt_16550_context *ctx) { int c; int count; unsigned long base = ctx->base_addr; int mode = rt_16550_io_mode_from_ctx(ctx); /* if (uart->modem & MSR_CTS)*/ { for (count = ctx->tx_fifo; (count > 0) && (ctx->out_npend > 0); count--, ctx->out_npend--) { c = ctx->out_buf[ctx->out_head++]; rt_16550_reg_out(mode, base, THR, c); ctx->out_head &= (OUT_BUFFER_SIZE - 1); } } } static inline void rt_16550_stat_interrupt(struct rt_16550_context *ctx) { unsigned long base = ctx->base_addr; int mode = rt_16550_io_mode_from_ctx(ctx); ctx->status |= (rt_16550_reg_in(mode, base, LSR) & (RTSER_LSR_OVERRUN_ERR | RTSER_LSR_PARITY_ERR | RTSER_LSR_FRAMING_ERR | RTSER_LSR_BREAK_IND)); } static int rt_16550_interrupt(rtdm_irq_t * irq_context) { struct rt_16550_context *ctx; unsigned long base; int mode; int iir; uint64_t timestamp = rtdm_clock_read(); int rbytes = 0; int events = 0; int modem; int ret = RTDM_IRQ_NONE; ctx = rtdm_irq_get_arg(irq_context, struct rt_16550_context); base = ctx->base_addr; mode = rt_16550_io_mode_from_ctx(ctx); rtdm_lock_get(&ctx->lock); while (1) { iir = rt_16550_reg_in(mode, base, IIR) & IIR_MASK; if (iir & IIR_PIRQ) break; if (iir == IIR_RX) { rbytes += rt_16550_rx_interrupt(ctx, ×tamp); events |= RTSER_EVENT_RXPEND; } else if (iir == IIR_STAT) rt_16550_stat_interrupt(ctx); else if (iir == IIR_TX) rt_16550_tx_fill(ctx); else if (iir == IIR_MODEM) { modem = rt_16550_reg_in(mode, base, MSR); if (modem & (modem << 4)) events |= RTSER_EVENT_MODEMHI; if ((modem ^ 0xF0) & (modem << 4)) events |= RTSER_EVENT_MODEMLO; } ret = RTDM_IRQ_HANDLED; } if (ctx->in_nwait > 0) { if ((ctx->in_nwait <= rbytes) || ctx->status) { ctx->in_nwait = 0; rtdm_event_signal(&ctx->in_event); } else ctx->in_nwait -= rbytes; } if (ctx->status) { events |= RTSER_EVENT_ERRPEND; ctx->ier_status &= ~IER_STAT; } if (events & ctx->config.event_mask) { int old_events = ctx->ioc_events; ctx->last_timestamp = timestamp; ctx->ioc_events = events; if (!old_events) rtdm_event_signal(&ctx->ioc_event); } if ((ctx->ier_status & IER_TX) && (ctx->out_npend == 0)) { /* mask transmitter empty interrupt */ ctx->ier_status &= ~IER_TX; rtdm_event_signal(&ctx->out_event); } /* update interrupt mask */ rt_16550_reg_out(mode, base, IER, ctx->ier_status); rtdm_lock_put(&ctx->lock); return ret; } static int rt_16550_set_config(struct rt_16550_context *ctx, const struct rtser_config *config, uint64_t **in_history_ptr) { rtdm_lockctx_t lock_ctx; unsigned long base = ctx->base_addr; int mode = rt_16550_io_mode_from_ctx(ctx); int err = 0; /* make line configuration atomic and IRQ-safe */ rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); if (config->config_mask & RTSER_SET_BAUD) { int dev_id = rtdm_fd_minor(rtdm_private_to_fd(ctx)); int baud_div; ctx->config.baud_rate = config->baud_rate; baud_div = (baud_base[dev_id] + (ctx->config.baud_rate>>1)) / ctx->config.baud_rate; rt_16550_reg_out(mode, base, LCR, LCR_DLAB); rt_16550_reg_out(mode, base, DLL, baud_div & 0xff); rt_16550_reg_out(mode, base, DLM, baud_div >> 8); } if (config->config_mask & RTSER_SET_PARITY) ctx->config.parity = config->parity & PARITY_MASK; if (config->config_mask & RTSER_SET_DATA_BITS) ctx->config.data_bits = config->data_bits & DATA_BITS_MASK; if (config->config_mask & RTSER_SET_STOP_BITS) ctx->config.stop_bits = config->stop_bits & STOP_BITS_MASK; if (config->config_mask & (RTSER_SET_PARITY | RTSER_SET_DATA_BITS | RTSER_SET_STOP_BITS | RTSER_SET_BAUD)) { rt_16550_reg_out(mode, base, LCR, (ctx->config.parity << 3) | (ctx->config.stop_bits << 2) | ctx->config.data_bits); ctx->status = 0; ctx->ioc_events &= ~RTSER_EVENT_ERRPEND; } if (config->config_mask & RTSER_SET_FIFO_DEPTH) { ctx->config.fifo_depth = config->fifo_depth & FIFO_MASK; rt_16550_reg_out(mode, base, FCR, FCR_FIFO | FCR_RESET_RX | FCR_RESET_TX); rt_16550_reg_out(mode, base, FCR, FCR_FIFO | ctx->config.fifo_depth); } rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); /* Timeout manipulation is not atomic. The user is supposed to take care not to use and change timeouts at the same time. */ if (config->config_mask & RTSER_SET_TIMEOUT_RX) ctx->config.rx_timeout = config->rx_timeout; if (config->config_mask & RTSER_SET_TIMEOUT_TX) ctx->config.tx_timeout = config->tx_timeout; if (config->config_mask & RTSER_SET_TIMEOUT_EVENT) ctx->config.event_timeout = config->event_timeout; if (config->config_mask & RTSER_SET_TIMESTAMP_HISTORY) { /* change timestamp history atomically */ rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); if (config->timestamp_history & RTSER_RX_TIMESTAMP_HISTORY) { if (!ctx->in_history) { ctx->in_history = *in_history_ptr; *in_history_ptr = NULL; if (!ctx->in_history) err = -ENOMEM; } } else { *in_history_ptr = ctx->in_history; ctx->in_history = NULL; } rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); } if (config->config_mask & RTSER_SET_EVENT_MASK) { /* change event mask atomically */ rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); ctx->config.event_mask = config->event_mask & EVENT_MASK; ctx->ioc_events = 0; if ((config->event_mask & RTSER_EVENT_RXPEND) && (ctx->in_npend > 0)) ctx->ioc_events |= RTSER_EVENT_RXPEND; if ((config->event_mask & RTSER_EVENT_ERRPEND) && ctx->status) ctx->ioc_events |= RTSER_EVENT_ERRPEND; if (config->event_mask & (RTSER_EVENT_MODEMHI | RTSER_EVENT_MODEMLO)) /* enable modem status interrupt */ ctx->ier_status |= IER_MODEM; else /* disable modem status interrupt */ ctx->ier_status &= ~IER_MODEM; rt_16550_reg_out(mode, base, IER, ctx->ier_status); rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); } if (config->config_mask & RTSER_SET_HANDSHAKE) { /* change handshake atomically */ rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); ctx->config.handshake = config->handshake; switch (ctx->config.handshake) { case RTSER_RTSCTS_HAND: // ...? default: /* RTSER_NO_HAND */ ctx->mcr_status = RTSER_MCR_DTR | RTSER_MCR_RTS | RTSER_MCR_OUT2; break; } rt_16550_reg_out(mode, base, MCR, ctx->mcr_status); rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); } return err; } void rt_16550_cleanup_ctx(struct rt_16550_context *ctx) { rtdm_event_destroy(&ctx->in_event); rtdm_event_destroy(&ctx->out_event); rtdm_event_destroy(&ctx->ioc_event); rtdm_mutex_destroy(&ctx->out_lock); } int rt_16550_open(struct rtdm_fd *fd, int oflags) { struct rt_16550_context *ctx; int dev_id = rtdm_fd_minor(fd); int err; uint64_t *dummy; rtdm_lockctx_t lock_ctx; ctx = rtdm_fd_to_private(fd); /* IPC initialisation - cannot fail with used parameters */ rtdm_lock_init(&ctx->lock); rtdm_event_init(&ctx->in_event, 0); rtdm_event_init(&ctx->out_event, 0); rtdm_event_init(&ctx->ioc_event, 0); rtdm_mutex_init(&ctx->out_lock); rt_16550_init_io_ctx(dev_id, ctx); ctx->tx_fifo = tx_fifo[dev_id]; ctx->in_head = 0; ctx->in_tail = 0; ctx->in_npend = 0; ctx->in_nwait = 0; ctx->in_lock = 0; ctx->in_history = NULL; ctx->out_head = 0; ctx->out_tail = 0; ctx->out_npend = 0; ctx->ioc_events = 0; ctx->ioc_event_lock = 0; ctx->status = 0; ctx->saved_errors = 0; rt_16550_set_config(ctx, &default_config, &dummy); err = rtdm_irq_request(&ctx->irq_handle, irq[dev_id], rt_16550_interrupt, irqtype[dev_id], rtdm_fd_device(fd)->name, ctx); if (err) { /* reset DTR and RTS */ rt_16550_reg_out(rt_16550_io_mode_from_ctx(ctx), ctx->base_addr, MCR, 0); rt_16550_cleanup_ctx(ctx); return err; } rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); /* enable interrupts */ ctx->ier_status = IER_RX; rt_16550_reg_out(rt_16550_io_mode_from_ctx(ctx), ctx->base_addr, IER, IER_RX); rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); return 0; } void rt_16550_close(struct rtdm_fd *fd) { struct rt_16550_context *ctx; unsigned long base; int mode; uint64_t *in_history; rtdm_lockctx_t lock_ctx; ctx = rtdm_fd_to_private(fd); base = ctx->base_addr; mode = rt_16550_io_mode_from_ctx(ctx); rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); /* reset DTR and RTS */ rt_16550_reg_out(mode, base, MCR, 0); /* mask all UART interrupts and clear pending ones. */ rt_16550_reg_out(mode, base, IER, 0); rt_16550_reg_in(mode, base, IIR); rt_16550_reg_in(mode, base, LSR); rt_16550_reg_in(mode, base, RHR); rt_16550_reg_in(mode, base, MSR); in_history = ctx->in_history; ctx->in_history = NULL; rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); rtdm_irq_free(&ctx->irq_handle); rt_16550_cleanup_ctx(ctx); kfree(in_history); } int rt_16550_ioctl(struct rtdm_fd *fd, unsigned int request, void *arg) { rtdm_lockctx_t lock_ctx; struct rt_16550_context *ctx; int err = 0; unsigned long base; int mode; ctx = rtdm_fd_to_private(fd); base = ctx->base_addr; mode = rt_16550_io_mode_from_ctx(ctx); switch (request) { case RTSER_RTIOC_GET_CONFIG: if (rtdm_fd_is_user(fd)) err = rtdm_safe_copy_to_user(fd, arg, &ctx->config, sizeof(struct rtser_config)); else memcpy(arg, &ctx->config, sizeof(struct rtser_config)); break; case RTSER_RTIOC_SET_CONFIG: { struct rtser_config *config; struct rtser_config config_buf; uint64_t *hist_buf = NULL; config = (struct rtser_config *)arg; if (rtdm_fd_is_user(fd)) { err = rtdm_safe_copy_from_user(fd, &config_buf, arg, sizeof(struct rtser_config)); if (err) return err; config = &config_buf; } if ((config->config_mask & RTSER_SET_BAUD) && (config->baud_rate > baud_base[rtdm_fd_minor(fd)] || config->baud_rate <= 0)) /* invalid baudrate for this port */ return -EINVAL; if (config->config_mask & RTSER_SET_TIMESTAMP_HISTORY) { /* * Reflect the call to non-RT as we will likely * allocate or free the buffer. */ if (rtdm_in_rt_context()) return -ENOSYS; if (config->timestamp_history & RTSER_RX_TIMESTAMP_HISTORY) hist_buf = kmalloc(IN_BUFFER_SIZE * sizeof(nanosecs_abs_t), GFP_KERNEL); } rt_16550_set_config(ctx, config, &hist_buf); if (hist_buf) kfree(hist_buf); break; } case RTSER_RTIOC_GET_STATUS: { int status; rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); status = ctx->saved_errors | ctx->status; ctx->status = 0; ctx->saved_errors = 0; ctx->ioc_events &= ~RTSER_EVENT_ERRPEND; rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); if (rtdm_fd_is_user(fd)) { struct rtser_status status_buf; status_buf.line_status = rt_16550_reg_in(mode, base, LSR) | status; status_buf.modem_status = rt_16550_reg_in(mode, base, MSR); err = rtdm_safe_copy_to_user(fd, arg, &status_buf, sizeof(struct rtser_status)); } else { ((struct rtser_status *)arg)->line_status = rt_16550_reg_in(mode, base, LSR) | status; ((struct rtser_status *)arg)->modem_status = rt_16550_reg_in(mode, base, MSR); } break; } case RTSER_RTIOC_GET_CONTROL: if (rtdm_fd_is_user(fd)) err = rtdm_safe_copy_to_user(fd, arg, &ctx->mcr_status, sizeof(int)); else *(int *)arg = ctx->mcr_status; break; case RTSER_RTIOC_SET_CONTROL: { int new_mcr = (long)arg; rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); ctx->mcr_status = new_mcr; rt_16550_reg_out(mode, base, MCR, new_mcr); rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); break; } case RTSER_RTIOC_WAIT_EVENT: { struct rtser_event ev = { .rxpend_timestamp = 0 }; rtdm_toseq_t timeout_seq; if (!rtdm_in_rt_context()) return -ENOSYS; /* Only one waiter allowed, stop any further attempts here. */ if (test_and_set_bit(0, &ctx->ioc_event_lock)) return -EBUSY; rtdm_toseq_init(&timeout_seq, ctx->config.event_timeout); rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); while (!ctx->ioc_events) { /* Only enable error interrupt when the user waits for it. */ if (ctx->config.event_mask & RTSER_EVENT_ERRPEND) { ctx->ier_status |= IER_STAT; rt_16550_reg_out(mode, base, IER, ctx->ier_status); } rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); err = rtdm_event_timedwait(&ctx->ioc_event, ctx->config.event_timeout, &timeout_seq); if (err) { /* Device has been closed? */ if (err == -EIDRM) err = -EBADF; goto wait_unlock_out; } rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); } ev.events = ctx->ioc_events; ctx->ioc_events &= ~(RTSER_EVENT_MODEMHI | RTSER_EVENT_MODEMLO); ev.last_timestamp = ctx->last_timestamp; ev.rx_pending = ctx->in_npend; if (ctx->in_history) ev.rxpend_timestamp = ctx->in_history[ctx->in_head]; rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); if (rtdm_fd_is_user(fd)) err = rtdm_safe_copy_to_user(fd, arg, &ev, sizeof(struct rtser_event)); else memcpy(arg, &ev, sizeof(struct rtser_event)); wait_unlock_out: /* release the simple event waiter lock */ clear_bit(0, &ctx->ioc_event_lock); break; } case RTSER_RTIOC_BREAK_CTL: { int lcr = ((long)arg & RTSER_BREAK_SET) << 6; rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); lcr |= (ctx->config.parity << 3) | (ctx->config.stop_bits << 2) | ctx->config.data_bits; rt_16550_reg_out(mode, base, LCR, lcr); rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); break; } case RTIOC_PURGE: { int fcr = 0; rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); if ((long)arg & RTDM_PURGE_RX_BUFFER) { ctx->in_head = 0; ctx->in_tail = 0; ctx->in_npend = 0; ctx->status = 0; fcr |= FCR_FIFO | FCR_RESET_RX; rt_16550_reg_in(mode, base, RHR); } if ((long)arg & RTDM_PURGE_TX_BUFFER) { ctx->out_head = 0; ctx->out_tail = 0; ctx->out_npend = 0; fcr |= FCR_FIFO | FCR_RESET_TX; } if (fcr) { rt_16550_reg_out(mode, base, FCR, fcr); rt_16550_reg_out(mode, base, FCR, FCR_FIFO | ctx->config.fifo_depth); } rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); break; } default: err = -ENOTTY; } return err; } ssize_t rt_16550_read(struct rtdm_fd *fd, void *buf, size_t nbyte) { struct rt_16550_context *ctx; rtdm_lockctx_t lock_ctx; size_t read = 0; int pending; int block; int subblock; int in_pos; char *out_pos = (char *)buf; rtdm_toseq_t timeout_seq; ssize_t ret = -EAGAIN; /* for non-blocking read */ int nonblocking; if (nbyte == 0) return 0; if (rtdm_fd_is_user(fd) && !rtdm_rw_user_ok(fd, buf, nbyte)) return -EFAULT; ctx = rtdm_fd_to_private(fd); rtdm_toseq_init(&timeout_seq, ctx->config.rx_timeout); /* non-blocking is handled separately here */ nonblocking = (ctx->config.rx_timeout < 0); /* only one reader allowed, stop any further attempts here */ if (test_and_set_bit(0, &ctx->in_lock)) return -EBUSY; rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); while (1) { /* switch on error interrupt - the user is ready to listen */ if ((ctx->ier_status & IER_STAT) == 0) { ctx->ier_status |= IER_STAT; rt_16550_reg_out(rt_16550_io_mode_from_ctx(ctx), ctx->base_addr, IER, ctx->ier_status); } if (ctx->status) { if (ctx->status & RTSER_LSR_BREAK_IND) ret = -EPIPE; else ret = -EIO; ctx->saved_errors = ctx->status & (RTSER_LSR_OVERRUN_ERR | RTSER_LSR_PARITY_ERR | RTSER_LSR_FRAMING_ERR | RTSER_SOFT_OVERRUN_ERR); ctx->status = 0; break; } pending = ctx->in_npend; if (pending > 0) { block = subblock = (pending <= nbyte) ? pending : nbyte; in_pos = ctx->in_head; rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); /* Do we have to wrap around the buffer end? */ if (in_pos + subblock > IN_BUFFER_SIZE) { /* Treat the block between head and buffer end separately. */ subblock = IN_BUFFER_SIZE - in_pos; if (rtdm_fd_is_user(fd)) { if (rtdm_copy_to_user (fd, out_pos, &ctx->in_buf[in_pos], subblock) != 0) { ret = -EFAULT; goto break_unlocked; } } else memcpy(out_pos, &ctx->in_buf[in_pos], subblock); read += subblock; out_pos += subblock; subblock = block - subblock; in_pos = 0; } if (rtdm_fd_is_user(fd)) { if (rtdm_copy_to_user(fd, out_pos, &ctx->in_buf[in_pos], subblock) != 0) { ret = -EFAULT; goto break_unlocked; } } else memcpy(out_pos, &ctx->in_buf[in_pos], subblock); read += subblock; out_pos += subblock; nbyte -= block; rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); ctx->in_head = (ctx->in_head + block) & (IN_BUFFER_SIZE - 1); if ((ctx->in_npend -= block) == 0) ctx->ioc_events &= ~RTSER_EVENT_RXPEND; if (nbyte == 0) break; /* All requested bytes read. */ continue; } if (nonblocking) /* ret was set to EAGAIN in case of a real non-blocking call or contains the error returned by rtdm_event_wait[_until] */ break; ctx->in_nwait = nbyte; rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); ret = rtdm_event_timedwait(&ctx->in_event, ctx->config.rx_timeout, &timeout_seq); if (ret < 0) { if (ret == -EIDRM) { /* Device has been closed - return immediately. */ return -EBADF; } rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); nonblocking = 1; if (ctx->in_npend > 0) { /* Final turn: collect pending bytes before exit. */ continue; } ctx->in_nwait = 0; break; } rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); } rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); break_unlocked: /* Release the simple reader lock, */ clear_bit(0, &ctx->in_lock); if ((read > 0) && ((ret == 0) || (ret == -EAGAIN) || (ret == -ETIMEDOUT) || (ret == -EINTR))) ret = read; return ret; } ssize_t rt_16550_write(struct rtdm_fd *fd, const void *buf, size_t nbyte) { struct rt_16550_context *ctx; rtdm_lockctx_t lock_ctx; size_t written = 0; int free; int block; int subblock; int out_pos; int lsr; char *in_pos = (char *)buf; rtdm_toseq_t timeout_seq; ssize_t ret; if (nbyte == 0) return 0; if (rtdm_fd_is_user(fd) && !rtdm_read_user_ok(fd, buf, nbyte)) return -EFAULT; ctx = rtdm_fd_to_private(fd); rtdm_toseq_init(&timeout_seq, ctx->config.tx_timeout); /* Make write operation atomic. */ ret = rtdm_mutex_timedlock(&ctx->out_lock, ctx->config.tx_timeout, &timeout_seq); if (ret) return ret; while (nbyte > 0) { rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); free = OUT_BUFFER_SIZE - ctx->out_npend; if (free > 0) { block = subblock = (nbyte <= free) ? nbyte : free; out_pos = ctx->out_tail; rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); /* Do we have to wrap around the buffer end? */ if (out_pos + subblock > OUT_BUFFER_SIZE) { /* Treat the block between head and buffer end separately. */ subblock = OUT_BUFFER_SIZE - out_pos; if (rtdm_fd_is_user(fd)) { if (rtdm_copy_from_user (fd, &ctx->out_buf[out_pos], in_pos, subblock) != 0) { ret = -EFAULT; break; } } else memcpy(&ctx->out_buf[out_pos], in_pos, subblock); written += subblock; in_pos += subblock; subblock = block - subblock; out_pos = 0; } if (rtdm_fd_is_user(fd)) { if (rtdm_copy_from_user (fd, &ctx->out_buf[out_pos], in_pos, subblock) != 0) { ret = -EFAULT; break; } } else memcpy(&ctx->out_buf[out_pos], in_pos, block); written += subblock; in_pos += subblock; nbyte -= block; rtdm_lock_get_irqsave(&ctx->lock, lock_ctx); ctx->out_tail = (ctx->out_tail + block) & (OUT_BUFFER_SIZE - 1); ctx->out_npend += block; lsr = rt_16550_reg_in(rt_16550_io_mode_from_ctx(ctx), ctx->base_addr, LSR); if (lsr & RTSER_LSR_THR_EMTPY) rt_16550_tx_fill(ctx); if (ctx->out_npend > 0 && !(ctx->ier_status & IER_TX)) { /* unmask tx interrupt */ ctx->ier_status |= IER_TX; rt_16550_reg_out(rt_16550_io_mode_from_ctx(ctx), ctx->base_addr, IER, ctx->ier_status); } rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); continue; } rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx); ret = rtdm_event_timedwait(&ctx->out_event, ctx->config.tx_timeout, &timeout_seq); if (ret < 0) { if (ret == -EIDRM) { /* Device has been closed - return immediately. */ return -EBADF; } if (ret == -EWOULDBLOCK) { /* Fix error code for non-blocking mode. */ ret = -EAGAIN; } break; } } rtdm_mutex_unlock(&ctx->out_lock); if ((written > 0) && ((ret == 0) || (ret == -EAGAIN) || (ret == -ETIMEDOUT) || (ret == -EINTR))) ret = written; return ret; } static struct rtdm_driver uart16550A_driver = { .profile_info = RTDM_PROFILE_INFO(uart16550A, RTDM_CLASS_SERIAL, RTDM_SUBCLASS_16550A, RTSER_PROFILE_VER), .device_flags = RTDM_NAMED_DEVICE | RTDM_EXCLUSIVE, .device_count = MAX_DEVICES, .context_size = sizeof(struct rt_16550_context), .ops = { .open = rt_16550_open, .close = rt_16550_close, .ioctl_rt = rt_16550_ioctl, .ioctl_nrt = rt_16550_ioctl, .read_rt = rt_16550_read, .write_rt = rt_16550_write, }, }; void rt_16550_exit(void); int __init rt_16550_init(void) { struct rtdm_device *dev; unsigned long base; char *name; int mode; int err; int i; if (!rtdm_available()) return -ENODEV; rt_16550_pnp_init(); rt_16550_pci_init(); for (i = 0; i < MAX_DEVICES; i++) { if (!rt_16550_addr_param(i)) continue; err = -EINVAL; if (!irq[i] || !rt_16550_addr_param_valid(i)) goto cleanup_out; dev = kmalloc(sizeof(struct rtdm_device) + RTDM_MAX_DEVNAME_LEN, GFP_KERNEL); err = -ENOMEM; if (!dev) goto cleanup_out; dev->driver = &uart16550A_driver; dev->label = "rtser%d"; name = (char *)(dev + 1); ksformat(name, RTDM_MAX_DEVNAME_LEN, dev->label, i); err = rt_16550_init_io(i, name); if (err) goto kfree_out; if (baud_base[i] == 0) baud_base[i] = DEFAULT_BAUD_BASE; if (tx_fifo[i] == 0) tx_fifo[i] = DEFAULT_TX_FIFO; /* Mask all UART interrupts and clear pending ones. */ base = rt_16550_base_addr(i); mode = rt_16550_io_mode(i); rt_16550_reg_out(mode, base, IER, 0); rt_16550_reg_in(mode, base, IIR); rt_16550_reg_in(mode, base, LSR); rt_16550_reg_in(mode, base, RHR); rt_16550_reg_in(mode, base, MSR); err = rtdm_dev_register(dev); if (err) goto release_io_out; device[i] = dev; } return 0; release_io_out: rt_16550_release_io(i); kfree_out: kfree(dev); cleanup_out: rt_16550_exit(); return err; } void rt_16550_exit(void) { int i; for (i = 0; i < MAX_DEVICES; i++) if (device[i]) { rtdm_dev_unregister(device[i]); rt_16550_release_io(i); kfree(device[i]); } rt_16550_pci_cleanup(); rt_16550_pnp_cleanup(); } module_init(rt_16550_init); module_exit(rt_16550_exit);