Search code examples
clinuxlinux-kerneluartrtai

How can this function do "write" operation without writing in "Transmitter Register"?


Here's the code from rt_imx_uart.c :

static ssize_t rt_imx_uart_write(struct rtdm_fd *fd, const void *buf,
                size_t nbyte)
{
    struct rt_imx_uart_ctx *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 (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.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 (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;

            ctx->ier_status |= IER_TX;
            rt_imx_uart_start_tx(ctx);

            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.
                 */
                ret = -EBADF;
            }
            break;
        }
    }

    rtdm_mutex_unlock(&ctx->out_lock);

    if ((written > 0) && ((ret == 0) || (ret == -EAGAIN) ||
                  (ret == -ETIMEDOUT)))
        ret = written;

    return ret;
}

I understand this function is meant to be used when a user-space program wants to write into the device. However I dont understand how this function can do that, since nowhere in the program do we ever write into the Transmitter Register. The start_tx function used only enables a flag and that's all.

PS: here's the link for this driver: Link to the driver


Solution

  • It looks like the function puts the bytes in a buffer and enables the transmit interrupt. The interrupt service routine probably writes to the uart transmit register.