zoukankan      html  css  js  c++  java
  • rt serial 的编写

    /*
     * 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, &timestamp);
                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);

  • 相关阅读:
    Android测试提升效率批处理脚本
    iOS系统设备网络抓包工具介绍:越狱和不越狱的办法
    用于管理Linux系统中的各种服务的命令service命令
    linux sort 命令详解
    在loadrunner操作中,所碰见问题及解决方法
    Unable to connect to the remote server 问题(已经解决)
    均值、中位数、众数
    修改Android 界面颜色
    设置Android程序图标和程序标题
    Android中的EditText默认时不弹出软键盘的方法
  • 原文地址:https://www.cnblogs.com/zym0805/p/4813570.html
Copyright © 2011-2022 走看看