/*
* Copyright (C) 2005-2007 Jan Kiszka <jan.kiszka@web.de>.
*
* 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 <linux/version.h>
#include <linux/module.h>
#include <linux/ioport.h>
#include <asm/io.h>
#include <rtdm/rtserial.h>
#include <rtdm/rtdm_driver.h>
#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[<device>] */
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];
static unsigned int start_index;
compat_module_param_array(irq, uint, MAX_DEVICES, 0400);
compat_module_param_array(baud_base, uint, MAX_DEVICES, 0400);
compat_module_param_array(tx_fifo, int, MAX_DEVICES, 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");
module_param(start_index, uint, 0400);
MODULE_PARM_DESC(start_index, "First device instance number to be used");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jan.kiszka@web.de");
#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 (testbits(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 inline void rt_16550_tx_interrupt(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 (testbits(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_interrupt(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 (testbits(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 (testbits(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 (testbits(config->config_mask, RTSER_SET_BAUD)) {
int dev_id = container_of(((void *)ctx),
struct rtdm_dev_context,
dev_private)->device->device_id;
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 (testbits(config->config_mask, RTSER_SET_PARITY))
ctx->config.parity = config->parity & PARITY_MASK;
if (testbits(config->config_mask, RTSER_SET_DATA_BITS))
ctx->config.data_bits = config->data_bits & DATA_BITS_MASK;
if (testbits(config->config_mask, RTSER_SET_STOP_BITS))
ctx->config.stop_bits = config->stop_bits & STOP_BITS_MASK;
if (testbits(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 (testbits(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 (testbits(config->config_mask, RTSER_SET_TIMEOUT_RX))
ctx->config.rx_timeout = config->rx_timeout;
if (testbits(config->config_mask, RTSER_SET_TIMEOUT_TX))
ctx->config.tx_timeout = config->tx_timeout;
if (testbits(config->config_mask, RTSER_SET_TIMEOUT_EVENT))
ctx->config.event_timeout = config->event_timeout;
if (testbits(config->config_mask, RTSER_SET_TIMESTAMP_HISTORY)) {
/* change timestamp history atomically */
rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);
if (testbits
(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 (testbits(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 (testbits(config->event_mask, RTSER_EVENT_RXPEND) &&
(ctx->in_npend > 0))
ctx->ioc_events |= RTSER_EVENT_RXPEND;
if (testbits(config->event_mask, RTSER_EVENT_ERRPEND)
&& ctx->status)
ctx->ioc_events |= RTSER_EVENT_ERRPEND;
if (testbits(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 (testbits(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_dev_context *context,
rtdm_user_info_t * user_info, int oflags)
{
struct rt_16550_context *ctx;
int dev_id = context->device->device_id;
int err;
uint64_t *dummy;
rtdm_lockctx_t lock_ctx;
ctx = (struct rt_16550_context *)context->dev_private;
/* 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],
context->device->proc_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;
}
int rt_16550_close(struct rtdm_dev_context *context,
rtdm_user_info_t * user_info)
{
struct rt_16550_context *ctx;
unsigned long base;
int mode;
uint64_t *in_history;
rtdm_lockctx_t lock_ctx;
ctx = (struct rt_16550_context *)context->dev_private;
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);
return 0;
}
int rt_16550_ioctl(struct rtdm_dev_context *context,
rtdm_user_info_t * user_info,
unsigned int request, void *arg)
{
rtdm_lockctx_t lock_ctx;
struct rt_16550_context *ctx;
int err = 0;
unsigned long base;
int mode;
ctx = (struct rt_16550_context *)context->dev_private;
base = ctx->base_addr;
mode = rt_16550_io_mode_from_ctx(ctx);
switch (request) {
case RTSER_RTIOC_GET_CONFIG:
if (user_info)
err =
rtdm_safe_copy_to_user(user_info, 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 (user_info) {
err =
rtdm_safe_copy_from_user(user_info, &config_buf,
arg,
sizeof(struct
rtser_config));
if (err)
return err;
config = &config_buf;
}
if (testbits(config->config_mask, RTSER_SET_BAUD) &&
(config->baud_rate >
baud_base[context->device->device_id] ||
config->baud_rate <= 0))
/* invalid baudrate for this port */
return -EINVAL;
if (testbits(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 (testbits(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 (user_info) {
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(user_info, 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 (user_info)
err =
rtdm_safe_copy_to_user(user_info, 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 (testbits(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 (user_info)
err =
rtdm_safe_copy_to_user(user_info, 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_dev_context * context,
rtdm_user_info_t * user_info, 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 (user_info && !rtdm_rw_user_ok(user_info, buf, nbyte))
return -EFAULT;
ctx = (struct rt_16550_context *)context->dev_private;
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 (!testbits(ctx->ier_status, IER_STAT)) {
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 (testbits(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 (user_info) {
if (rtdm_copy_to_user
(user_info, 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 (user_info) {
if (rtdm_copy_to_user(user_info, 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_dev_context * context,
rtdm_user_info_t * user_info, 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;
char *in_pos = (char *)buf;
rtdm_toseq_t timeout_seq;
ssize_t ret;
if (nbyte == 0)
return 0;
if (user_info && !rtdm_read_user_ok(user_info, buf, nbyte))
return -EFAULT;
ctx = (struct rt_16550_context *)context->dev_private;
rtdm_toseq_init(&timeout_seq, ctx->config.rx_timeout);
/* Make write operation atomic. */
ret = rtdm_mutex_timedlock(&ctx->out_lock, ctx->config.rx_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 (user_info) {
if (rtdm_copy_from_user
(user_info,
&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 (user_info) {
if (rtdm_copy_from_user
(user_info, &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;
/* 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 const struct rtdm_device __initdata device_tmpl = {
.struct_version = RTDM_DEVICE_STRUCT_VER,
.device_flags = RTDM_NAMED_DEVICE | RTDM_EXCLUSIVE,
.context_size = sizeof(struct rt_16550_context),
.device_name = "",
.open_nrt = rt_16550_open,
.ops = {
.close_nrt = rt_16550_close,
.ioctl_rt = rt_16550_ioctl,
.ioctl_nrt = rt_16550_ioctl,
.read_rt = rt_16550_read,
.write_rt = rt_16550_write,
},
.device_class = RTDM_CLASS_SERIAL,
.device_sub_class = RTDM_SUBCLASS_16550A,
.profile_version = RTSER_PROFILE_VER,
.driver_name = RT_16550_DRIVER_NAME,
.driver_version = RTDM_DRIVER_VER(1, 5, 2),
.peripheral_name = "UART 16550A",
.provider_name = "Jan Kiszka",
};
void rt_16550_exit(void);
int __init rt_16550_init(void)
{
struct rtdm_device *dev;
unsigned long base;
int mode;
int err;
int i;
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), GFP_KERNEL);
err = -ENOMEM;
if (!dev)
goto cleanup_out;
memcpy(dev, &device_tmpl, sizeof(struct rtdm_device));
snprintf(dev->device_name, RTDM_MAX_DEVNAME_LEN, "rtser%d",
start_index + i);
dev->device_id = i;
dev->proc_name = dev->device_name;
err = rt_16550_init_io(i, dev->device_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], 1000);
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);