linux uart console driver(2)--ttySn设备节点的操作

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dongkun152/article/details/81279378

linux uart console driver(2)–ttySn设备节点的操作

在上节中有tty_dev_add()注册三个字符设备驱动。

static int tty_cdev_add(struct tty_driver *driver, dev_t dev,
        unsigned int index, unsigned int count)
{
    /* init here, since reused cdevs cause crashes */
    cdev_init(&driver->cdevs[index], &tty_fops);
    driver->cdevs[index].owner = driver->owner;
    return cdev_add(&driver->cdevs[index], dev, count);
}

如以上代码,字符驱动的file_operation是tty_fops. 在tty_io.c 中有

static const struct file_operations tty_fops = {
    .llseek     = no_llseek,
    .read       = tty_read,
    .write      = tty_write,
    .poll       = tty_poll,
    .unlocked_ioctl = tty_ioctl,
    .compat_ioctl   = tty_compat_ioctl,
    .open       = tty_open,
    .release    = tty_release,
    .fasync     = tty_fasync,
};

static const struct file_operations console_fops = {
    .llseek     = no_llseek,
    .read       = tty_read,
    .write      = redirected_tty_write,
    .poll       = tty_poll,
    .unlocked_ioctl = tty_ioctl,
    .compat_ioctl   = tty_compat_ioctl,
    .open       = tty_open,
    .release    = tty_release,
    .fasync     = tty_fasync,
};
int __init tty_init(void)
{
    cdev_init(&tty_cdev, &tty_fops);
    if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
        register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
        panic("Couldn't register /dev/tty driver\n");
    device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");

    cdev_init(&console_cdev, &console_fops);
    if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
        register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0)
        panic("Couldn't register /dev/console driver\n");
    consdev = device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL,
                  "console");
    if (IS_ERR(consdev))
        consdev = NULL;
    else
        WARN_ON(device_create_file(consdev, &dev_attr_active) < 0);

#ifdef CONFIG_VT
    vty_init(&console_fops);
#endif
    return 0;
}

所以当打开ttySn节点是,会执行tty_open函数。

打开ttySn节点

tty_open

//关键数据结构 和变量
struct tty_file_private {
    struct tty_struct *tty;
    struct file *file;
    struct list_head list;
};
struct tty_struct {
    int magic;
    struct kref kref;
    struct device *dev;
    struct tty_driver *driver;
    const struct tty_operations *ops;
    int index;

    /* Protects ldisc changes: Lock tty not pty */
    struct mutex ldisc_mutex;
    struct tty_ldisc *ldisc;

    struct mutex atomic_write_lock;
    struct mutex legacy_mutex;
    struct mutex termios_mutex;
    spinlock_t ctrl_lock;
    /* Termios values are protected by the termios mutex */
    struct ktermios termios, termios_locked;
    struct termiox *termiox;    /* May be NULL for unsupported */
    char name[64];
    struct pid *pgrp;       /* Protected by ctrl lock */
    struct pid *session;
    unsigned long flags;
    int count;
    struct winsize winsize;     /* termios mutex */
    unsigned char stopped:1, hw_stopped:1, flow_stopped:1, packet:1;
    unsigned char ctrl_status;  /* ctrl_lock */
    unsigned int receive_room;  /* Bytes free for queue */
    int flow_change;

    struct tty_struct *link;
    struct fasync_struct *fasync;
    int alt_speed;      /* For magic substitution of 38400 bps */
    wait_queue_head_t write_wait;
    wait_queue_head_t read_wait;
    struct work_struct hangup_work;
    void *disc_data;
    void *driver_data;
    struct list_head tty_files;

#define N_TTY_BUF_SIZE 4096

    unsigned char closing:1;
    unsigned short minimum_to_wake;
    unsigned char *write_buf;
    int write_cnt;
    /* If the tty has a pending do_SAK, queue it here - akpm */
    struct work_struct SAK_work;
    struct tty_port *port;
};

struct tty_ldisc {
    struct tty_ldisc_ops *ops;
    atomic_t users;
    wait_queue_head_t wq_idle;
};
struct tty_ldisc_ops {
    int magic;
    char    *name;
    int num;
    int flags;

    /*
     * The following routines are called from above.
     */
    int (*open)(struct tty_struct *);
    void    (*close)(struct tty_struct *);
    void    (*flush_buffer)(struct tty_struct *tty);
    ssize_t (*chars_in_buffer)(struct tty_struct *tty);
    ssize_t (*read)(struct tty_struct *tty, struct file *file,
            unsigned char __user *buf, size_t nr);
    ssize_t (*write)(struct tty_struct *tty, struct file *file,
             const unsigned char *buf, size_t nr);
    int (*ioctl)(struct tty_struct *tty, struct file *file,
             unsigned int cmd, unsigned long arg);
    long    (*compat_ioctl)(struct tty_struct *tty, struct file *file,
                unsigned int cmd, unsigned long arg);
    void    (*set_termios)(struct tty_struct *tty, struct ktermios *old);
    unsigned int (*poll)(struct tty_struct *, struct file *,
                 struct poll_table_struct *);
    int (*hangup)(struct tty_struct *tty);

    /*
     * The following routines are called from below.
     */
    void    (*receive_buf)(struct tty_struct *, const unsigned char *cp,
                   char *fp, int count);
    void    (*write_wakeup)(struct tty_struct *);
    void    (*dcd_change)(struct tty_struct *, unsigned int);

    struct  module *owner;

    int refcount;
};
struct tty_ldisc_ops tty_ldisc_N_TTY = {
    .magic           = TTY_LDISC_MAGIC,
    .name            = "n_tty",
    .open            = n_tty_open,
    .close           = n_tty_close,
    .flush_buffer    = n_tty_flush_buffer,
    .chars_in_buffer = n_tty_chars_in_buffer,
    .read            = n_tty_read,
    .write           = n_tty_write,
    .ioctl           = n_tty_ioctl,
    .set_termios     = n_tty_set_termios,
    .poll            = n_tty_poll,
    .receive_buf     = n_tty_receive_buf,
    .write_wakeup    = n_tty_write_wakeup
};
static const struct tty_operations uart_ops = {
    .open       = uart_open,
    .close      = uart_close,
    .write      = uart_write,
    .put_char   = uart_put_char,
    .flush_chars    = uart_flush_chars,
    .write_room = uart_write_room,
    .chars_in_buffer= uart_chars_in_buffer,
    .flush_buffer   = uart_flush_buffer,
    .ioctl      = uart_ioctl,
    .throttle   = uart_throttle,
    .unthrottle = uart_unthrottle,
    .send_xchar = uart_send_xchar,
    .set_termios    = uart_set_termios,
    .set_ldisc  = uart_set_ldisc,
    .stop       = uart_stop,
    .start      = uart_start,
    .hangup     = uart_hangup,
    .break_ctl  = uart_break_ctl,
    .wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
    .proc_fops  = &uart_proc_fops,
#endif
    .tiocmget   = uart_tiocmget,
    .tiocmset   = uart_tiocmset,
    .get_icount = uart_get_icount,
#ifdef CONFIG_CONSOLE_POLL
    .poll_init  = uart_poll_init,
    .poll_get_char  = uart_poll_get_char,
    .poll_put_char  = uart_poll_put_char,
#endif
};
/**
 *  tty_open        -   open a tty device
 *  @inode: inode of device file
 *  @filp: file pointer to tty
 *
 *  tty_open and tty_release keep up the tty count that contains the
 *  number of opens done on a tty. We cannot use the inode-count, as
 *  different inodes might point to the same tty.
 *
 *  Open-counting is needed for pty masters, as well as for keeping
 *  track of serial lines: DTR is dropped when the last close happens.
 *  (This is not done solely through tty->count, now.  - Ted 1/27/92)
 *
 *  The termios state of a pty is reset on first open so that
 *  settings don't persist across reuse.
 *
 *  Locking: tty_mutex protects tty, tty_lookup_driver and tty_init_dev.
 *       tty->count should protect the rest.
 *       ->siglock protects ->signal/->sighand
 *
 *  Note: the tty_unlock/lock cases without a ref are only safe due to
 *  tty_mutex
 */

static int tty_open(struct inode *inode, struct file *filp)
{
    struct tty_struct *tty;
    int noctty, retval;
    struct tty_driver *driver = NULL;
    int index;
    dev_t device = inode->i_rdev;//设备号
    unsigned saved_flags = filp->f_flags;

    nonseekable_open(inode, filp);

retry_open:
    retval = tty_alloc_file(filp); //分配struct tty_file_private结构,准备关联相关tty_struct
    if (retval)
        return -ENOMEM;

    noctty = filp->f_flags & O_NOCTTY;
    index  = -1;
    retval = 0;

    mutex_lock(&tty_mutex);
    /* This is protected by the tty_mutex */
    tty = tty_open_current_tty(device, filp); //当前打开的并非tty,而是ttySn, 返回NULL
    if (IS_ERR(tty)) {
        retval = PTR_ERR(tty);
        goto err_unlock;
    } else if (!tty) {
        driver = tty_lookup_driver(device, filp, &noctty, &index);//查找tty_driver
        if (IS_ERR(driver)) {
            retval = PTR_ERR(driver);
            goto err_unlock;
        }
//此时已经获取到已经注册的tty_driver
        /* check whether we're reopening an existing tty */
        tty = tty_driver_lookup_tty(driver, inode, index); //此时还没有tty_struct
        if (IS_ERR(tty)) {
            retval = PTR_ERR(tty);
            goto err_unlock;
        }
    }

    if (tty) {
        tty_lock(tty);
        retval = tty_reopen(tty);
        if (retval < 0) {
            tty_unlock(tty);
            tty = ERR_PTR(retval);
        }
    } else  /* Returns with the tty_lock held for now */ //tty = NULL
        tty = tty_init_dev(driver, index); //分配新的tty_struct

    mutex_unlock(&tty_mutex);
    if (driver)
        tty_driver_kref_put(driver);
    if (IS_ERR(tty)) {
        retval = PTR_ERR(tty);
        goto err_file;
    }

    tty_add_file(tty, filp);//将file结构关联到tty_file_private 结构中,并将tty_file_private加入到tty_struct的tty_files链表中

    check_tty_count(tty, __func__);
    if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
        tty->driver->subtype == PTY_TYPE_MASTER)
        noctty = 1;
#ifdef TTY_DEBUG_HANGUP
    printk(KERN_DEBUG "%s: opening %s...\n", __func__, tty->name);
#endif
    if (tty->ops->open)
        retval = tty->ops->open(tty, filp);//调用uart_ops的open方法
    else
        retval = -ENODEV;
    filp->f_flags = saved_flags;

    if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) &&
                        !capable(CAP_SYS_ADMIN))
        retval = -EBUSY;

    if (retval) {
#ifdef TTY_DEBUG_HANGUP
        printk(KERN_DEBUG "%s: error %d in opening %s...\n", __func__,
                retval, tty->name);
#endif
        tty_unlock(tty); /* need to call tty_release without BTM */
        tty_release(inode, filp);
        if (retval != -ERESTARTSYS)
            return retval;

        if (signal_pending(current))
            return retval;

        schedule();
        /*
         * Need to reset f_op in case a hangup happened.
         */
        if (filp->f_op == &hung_up_tty_fops)
            filp->f_op = &tty_fops;
        goto retry_open;
    }
    tty_unlock(tty);


    mutex_lock(&tty_mutex);
    tty_lock(tty);
    spin_lock_irq(&current->sighand->siglock);
    if (!noctty &&
        current->signal->leader &&
        !current->signal->tty &&
        tty->session == NULL)
        __proc_set_tty(current, tty);
    spin_unlock_irq(&current->sighand->siglock);
    tty_unlock(tty);
    mutex_unlock(&tty_mutex);
    return 0;
err_unlock:
    mutex_unlock(&tty_mutex);
    /* after locks to avoid deadlock */
    if (!IS_ERR_OR_NULL(driver))
        tty_driver_kref_put(driver);
err_file:
    tty_free_file(filp);
    return retval;
}

int tty_alloc_file(struct file *file)
{
    struct tty_file_private *priv;

    priv = kmalloc(sizeof(*priv), GFP_KERNEL); //分配tty_file_private
    if (!priv)
        return -ENOMEM;

    file->private_data = priv;

    return 0;
}
/**
 *  tty_open_current_tty - get tty of current task for open
 *  @device: device number
 *  @filp: file pointer to tty
 *  @return: tty of the current task iff @device is /dev/tty
 *
 *  We cannot return driver and index like for the other nodes because
 *  devpts will not work then. It expects inodes to be from devpts FS.
 *
 *  We need to move to returning a refcounted object from all the lookup
 *  paths including this one.
 */
static struct tty_struct *tty_open_current_tty(dev_t device, struct file *filp)
{
    struct tty_struct *tty;

    if (device != MKDEV(TTYAUX_MAJOR, 0))//open ttySn时直接返回 NULL
        return NULL;

    tty = get_current_tty();
    if (!tty)
        return ERR_PTR(-ENXIO);

    filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
    /* noctty = 1; */
    tty_kref_put(tty);
    /* FIXME: we put a reference and return a TTY! */
    /* This is only safe because the caller holds tty_mutex */
    return tty;
}
/**
 *  tty_lookup_driver - lookup a tty driver for a given device file
 *  @device: device number
 *  @filp: file pointer to tty
 *  @noctty: set if the device should not become a controlling tty
 *  @index: index for the device in the @return driver
 *  @return: driver for this inode (with increased refcount)
 *
 *  If @return is not erroneous, the caller is responsible to decrement the
 *  refcount by tty_driver_kref_put.
 *
 *  Locking: tty_mutex protects get_tty_driver
 */
static struct tty_driver *tty_lookup_driver(dev_t device, struct file *filp,
        int *noctty, int *index)
{
    struct tty_driver *driver;

    switch (device) {
#ifdef CONFIG_VT
    case MKDEV(TTY_MAJOR, 0): {
        extern struct tty_driver *console_driver;
        driver = tty_driver_kref_get(console_driver);
        *index = fg_console;
        *noctty = 1;
        break;
    }
#endif
    case MKDEV(TTYAUX_MAJOR, 1): {
        struct tty_driver *console_driver = console_device(index);
        if (console_driver) {
            driver = tty_driver_kref_get(console_driver);
            if (driver) {
                /* Don't let /dev/console block */
                filp->f_flags |= O_NONBLOCK;
                *noctty = 1;
                break;
            }
        }
        return ERR_PTR(-ENODEV);
    }
    default: //ttySn 设备时走这里
        driver = get_tty_driver(device, index);
        if (!driver)
            return ERR_PTR(-ENODEV);
        break;
    }
    return driver;
}
/**
 *  get_tty_driver      -   find device of a tty
 *  @dev_t: device identifier
 *  @index: returns the index of the tty
 *
 *  This routine returns a tty driver structure, given a device number
 *  and also passes back the index number.
 *
 *  Locking: caller must hold tty_mutex
 */

static struct tty_driver *get_tty_driver(dev_t device, int *index)
{
    struct tty_driver *p;

    list_for_each_entry(p, &tty_drivers, tty_drivers) { //在tty_drivers 全局链表中根据设备号查找ttydriver
        dev_t base = MKDEV(p->major, p->minor_start);
        if (device < base || device >= base + p->num)
            continue;
        *index = device - base;
        return tty_driver_kref_get(p);
    }
    return NULL;
}
/**
 *  tty_init_dev        -   initialise a tty device
 *  @driver: tty driver we are opening a device on
 *  @idx: device index
 *  @ret_tty: returned tty structure
 *
 *  Prepare a tty device. This may not be a "new" clean device but
 *  could also be an active device. The pty drivers require special
 *  handling because of this.
 *
 *  Locking:
 *      The function is called under the tty_mutex, which
 *  protects us from the tty struct or driver itself going away.
 *
 *  On exit the tty device has the line discipline attached and
 *  a reference count of 1. If a pair was created for pty/tty use
 *  and the other was a pty master then it too has a reference count of 1.
 *
 * WSH 06/09/97: Rewritten to remove races and properly clean up after a
 * failed open.  The new code protects the open with a mutex, so it's
 * really quite straightforward.  The mutex locking can probably be
 * relaxed for the (most common) case of reopening a tty.
 */

struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx)
{
    struct tty_struct *tty;
    int retval;

    /*
     * First time open is complex, especially for PTY devices.
     * This code guarantees that either everything succeeds and the
     * TTY is ready for operation, or else the table slots are vacated
     * and the allocated memory released.  (Except that the termios
     * and locked termios may be retained.)
     */

    if (!try_module_get(driver->owner))
        return ERR_PTR(-ENODEV);

    tty = alloc_tty_struct();//分配tty_struct
    if (!tty) {
        retval = -ENOMEM;
        goto err_module_put;
    }
    initialize_tty_struct(tty, driver, idx); //初始化tty_struct

    tty_lock(tty);
    retval = tty_driver_install_tty(driver, tty);//安装tty_struct
    if (retval < 0)
        goto err_deinit_tty;

    if (!tty->port)
        tty->port = driver->ports[idx]; //将对应的tty_port 关联到tty_struct中

    WARN_RATELIMIT(!tty->port,
            "%s: %s driver does not set tty->port. This will crash the kernel later. Fix the driver!\n",
            __func__, tty->driver->name);

    tty->port->itty = tty;//tty_port的输入itty就是此tty

    /*
     * Structures all installed ... call the ldisc open routines.
     * If we fail here just call release_tty to clean up.  No need
     * to decrement the use counts, as release_tty doesn't care.
     */
    retval = tty_ldisc_setup(tty, tty->link); //打开线路规程
    if (retval)
        goto err_release_tty;
    /* Return the tty locked so that it cannot vanish under the caller */
    return tty;

err_deinit_tty:
    tty_unlock(tty);
    deinitialize_tty_struct(tty);
    free_tty_struct(tty);
err_module_put:
    module_put(driver->owner);
    return ERR_PTR(retval);

    /* call the tty release_tty routine to clean out this slot */
err_release_tty:
    tty_unlock(tty);
    printk_ratelimited(KERN_INFO "tty_init_dev: ldisc open failed, "
                 "clearing slot %d\n", idx);
    release_tty(tty, idx);
    return ERR_PTR(retval);
}
/**
 *  initialize_tty_struct
 *  @tty: tty to initialize
 *
 *  This subroutine initializes a tty structure that has been newly
 *  allocated.
 *
 *  Locking: none - tty in question must not be exposed at this point
 */

void initialize_tty_struct(struct tty_struct *tty,
        struct tty_driver *driver, int idx)
{
    memset(tty, 0, sizeof(struct tty_struct));
    kref_init(&tty->kref);
    tty->magic = TTY_MAGIC;
    tty_ldisc_init(tty); //设置tty的线路规程,它的ops是tty_ldisc_N_TTY
    tty->session = NULL;
    tty->pgrp = NULL;
    mutex_init(&tty->legacy_mutex);
    mutex_init(&tty->termios_mutex);
    mutex_init(&tty->ldisc_mutex);
    init_waitqueue_head(&tty->write_wait);
    init_waitqueue_head(&tty->read_wait);
    INIT_WORK(&tty->hangup_work, do_tty_hangup);
    mutex_init(&tty->atomic_write_lock);
    spin_lock_init(&tty->ctrl_lock);
    INIT_LIST_HEAD(&tty->tty_files);
    INIT_WORK(&tty->SAK_work, do_SAK_work);

    tty->driver = driver;
    tty->ops = driver->ops; //设置tty_operation,此时为uart_ops
    tty->index = idx;
    tty_line_name(driver, idx, tty->name);
    tty->dev = tty_get_device(tty); //获取到当时注册到驱动模型中的device
}
/**
 *  tty_driver_install_tty() - install a tty entry in the driver
 *  @driver: the driver for the tty
 *  @tty: the tty
 *
 *  Install a tty object into the driver tables. The tty->index field
 *  will be set by the time this is called. This method is responsible
 *  for ensuring any need additional structures are allocated and
 *  configured.
 *
 *  Locking: tty_mutex for now
 */
static int tty_driver_install_tty(struct tty_driver *driver,
                        struct tty_struct *tty)
{
    return driver->ops->install ? driver->ops->install(driver, tty) :
        tty_standard_install(driver, tty); //这里使用的是标准安装
}
int tty_standard_install(struct tty_driver *driver, struct tty_struct *tty)
{
    int ret = tty_init_termios(tty);
    if (ret)
        return ret;

    tty_driver_kref_get(driver);
    tty->count++;
    driver->ttys[tty->index] = tty; //将tty加入到tty_driver的tty_struct指针数组中,安装完毕
    return 0;
}
EXPORT_SYMBOL_GPL(tty_standard_install);
/*
 * Calls to uart_open are serialised by the tty_lock in
 *   drivers/tty/tty_io.c:tty_open()
 * Note that if this fails, then uart_close() _will_ be called.
 *
 * In time, we want to scrap the "opening nonpresent ports"
 * behaviour and implement an alternative way for setserial
 * to set base addresses/ports/types.  This will allow us to
 * get rid of a certain amount of extra tests.
 */
static int uart_open(struct tty_struct *tty, struct file *filp)
{
    struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state;
    int retval, line = tty->index;
    struct uart_state *state = drv->state + line;
    struct tty_port *port = &state->port;

    pr_debug("uart_open(%d) called\n", line);

    /*
     * We take the semaphore here to guarantee that we won't be re-entered
     * while allocating the state structure, or while we request any IRQs
     * that the driver may need.  This also has the nice side-effect that
     * it delays the action of uart_hangup, so we can guarantee that
     * state->port.tty will always contain something reasonable.
     */
    if (mutex_lock_interruptible(&port->mutex)) {
        retval = -ERESTARTSYS;
        goto end;
    }

    port->count++;
    if (!state->uart_port || state->uart_port->flags & UPF_DEAD) {
        retval = -ENXIO;
        goto err_dec_count;
    }

    /*
     * Once we set tty->driver_data here, we are guaranteed that
     * uart_close() will decrement the driver module use count.
     * Any failures from here onwards should not touch the count.
     */
    tty->driver_data = state;
    state->uart_port->state = state;
    state->port.low_latency =
        (state->uart_port->flags & UPF_LOW_LATENCY) ? 1 : 0;
    tty_port_tty_set(port, tty);

    /*
     * If the port is in the middle of closing, bail out now.
     */
    if (tty_hung_up_p(filp)) {
        retval = -EAGAIN;
        goto err_dec_count;
    }

    /*
     * Make sure the device is in D0 state.
     */
    if (port->count == 1)
        uart_change_pm(state, UART_PM_STATE_ON);

    /*
     * Start up the serial port.
     */
    retval = uart_startup(tty, state, 0);

    /*
     * If we succeeded, wait until the port is ready.
     */
    mutex_unlock(&port->mutex);
    if (retval == 0)
        retval = tty_port_block_til_ready(port, tty, filp);

end:
    return retval;
err_dec_count:
    port->count--;
    mutex_unlock(&port->mutex);
    goto end;
}
static int uart_startup(struct tty_struct *tty, struct uart_state *state,
        int init_hw)
{
    struct tty_port *port = &state->port;
    int retval;

    if (port->flags & ASYNC_INITIALIZED)
        return 0;

    /*
     * Set the TTY IO error marker - we will only clear this
     * once we have successfully opened the port.
     */
    set_bit(TTY_IO_ERROR, &tty->flags);

    retval = uart_port_startup(tty, state, init_hw);
    if (!retval) {
        set_bit(ASYNCB_INITIALIZED, &port->flags);
        clear_bit(TTY_IO_ERROR, &tty->flags);
    } else if (retval > 0)
        retval = 0;

    return retval;
}
/*
 * Startup the port.  This will be called once per open.  All calls
 * will be serialised by the per-port mutex.
 */
static int uart_port_startup(struct tty_struct *tty, struct uart_state *state,
        int init_hw)
{
    struct uart_port *uport = state->uart_port;
    struct tty_port *port = &state->port;
    unsigned long page;
    int retval = 0;

    if (uport->type == PORT_UNKNOWN)
        return 1;

    /*
     * Initialise and allocate the transmit and temporary
     * buffer.//分配传输时用的buffer
     */
    if (!state->xmit.buf) { 
        /* This is protected by the per port mutex */
        page = get_zeroed_page(GFP_KERNEL); //分配一个空的页
        if (!page)
            return -ENOMEM;

        state->xmit.buf = (unsigned char *) page;
        uart_circ_clear(&state->xmit);
    }

    retval = uport->ops->startup(uport);//调用硬件的startup函数serial8250_startup
    if (retval == 0) {
        if (uart_console(uport) && uport->cons->cflag) {
            tty->termios.c_cflag = uport->cons->cflag;
            uport->cons->cflag = 0;
        }
        /*
         * Initialise the hardware port settings.
         */
        uart_change_speed(tty, state, NULL);

        if (init_hw) {
            /*
             * Setup the RTS and DTR signals once the
             * port is open and ready to respond.
             */
            if (tty->termios.c_cflag & CBAUD)
                uart_set_mctrl(uport, TIOCM_RTS | TIOCM_DTR);
        }

        if (tty_port_cts_enabled(port)) {
            spin_lock_irq(&uport->lock);
            if (!(uport->ops->get_mctrl(uport) & TIOCM_CTS))
                tty->hw_stopped = 1;
            spin_unlock_irq(&uport->lock);
        }
    }

    /*
     * This is to allow setserial on this port. People may want to set
     * port/irq/type and then reconfigure the port properly if it failed
     * now.
     */
    if (retval && capable(CAP_SYS_ADMIN))
        return 1;

    return retval;
}

static int serial8250_startup(struct uart_port *port)
{
    struct uart_8250_port *up =
        container_of(port, struct uart_8250_port, port);
    unsigned long flags;
    unsigned char lsr, iir;
    int retval;

    if (port->type == PORT_8250_CIR)
        return -ENODEV;

    if (!port->fifosize)
        port->fifosize = uart_config[port->type].fifo_size;
    if (!up->tx_loadsz)
        up->tx_loadsz = uart_config[port->type].tx_loadsz;
    if (!up->capabilities)
        up->capabilities = uart_config[port->type].flags;
    up->mcr = 0;

    if (port->iotype != up->cur_iotype)
        set_io_from_upio(port);

    if (port->type == PORT_16C950) {
        /* Wake up and initialize UART */
        up->acr = 0;
        serial_port_out(port, UART_LCR, UART_LCR_CONF_MODE_B);
        serial_port_out(port, UART_EFR, UART_EFR_ECB);
        serial_port_out(port, UART_IER, 0);
        serial_port_out(port, UART_LCR, 0);
        serial_icr_write(up, UART_CSR, 0); /* Reset the UART */
        serial_port_out(port, UART_LCR, UART_LCR_CONF_MODE_B);
        serial_port_out(port, UART_EFR, UART_EFR_ECB);
        serial_port_out(port, UART_LCR, 0);
    }

#ifdef CONFIG_SERIAL_8250_RSA
    /*
     * If this is an RSA port, see if we can kick it up to the
     * higher speed clock.
     */
    enable_rsa(up);
#endif

    /*
     * Clear the FIFO buffers and disable them.
     * (they will be reenabled in set_termios())
     */
    serial8250_clear_fifos(up);

    /*
     * Clear the interrupt registers.
     */
    serial_port_in(port, UART_LSR); //调用注册的serial_in函数设置相关寄存器
    serial_port_in(port, UART_RX);
    serial_port_in(port, UART_IIR);
    serial_port_in(port, UART_MSR);

    /*
     * At this point, there's no way the LSR could still be 0xff;
     * if it is, then bail out, because there's likely no UART
     * here.
     */
    if (!(port->flags & UPF_BUGGY_UART) &&
        (serial_port_in(port, UART_LSR) == 0xff)) {
        printk_ratelimited(KERN_INFO "ttyS%d: LSR safety check engaged!\n",
                   serial_index(port));
        return -ENODEV;
    }

    /*
     * For a XR16C850, we need to set the trigger levels
     */
    if (port->type == PORT_16850) {
        unsigned char fctr;

        serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);

        fctr = serial_in(up, UART_FCTR) & ~(UART_FCTR_RX|UART_FCTR_TX);
        serial_port_out(port, UART_FCTR,
                fctr | UART_FCTR_TRGD | UART_FCTR_RX);
        serial_port_out(port, UART_TRG, UART_TRG_96);
        serial_port_out(port, UART_FCTR,
                fctr | UART_FCTR_TRGD | UART_FCTR_TX);
        serial_port_out(port, UART_TRG, UART_TRG_96);

        serial_port_out(port, UART_LCR, 0);
    }

    if (port->irq) {
        unsigned char iir1;
        /*
         * Test for UARTs that do not reassert THRE when the
         * transmitter is idle and the interrupt has already
         * been cleared.  Real 16550s should always reassert
         * this interrupt whenever the transmitter is idle and
         * the interrupt is enabled.  Delays are necessary to
         * allow register changes to become visible.
         */
        spin_lock_irqsave(&port->lock, flags);
        if (up->port.irqflags & IRQF_SHARED)
            disable_irq_nosync(port->irq);

        wait_for_xmitr(up, UART_LSR_THRE);
        serial_port_out_sync(port, UART_IER, UART_IER_THRI);
        udelay(1); /* allow THRE to set */
        iir1 = serial_port_in(port, UART_IIR);
        serial_port_out(port, UART_IER, 0);
        serial_port_out_sync(port, UART_IER, UART_IER_THRI);
        udelay(1); /* allow a working UART time to re-assert THRE */
        iir = serial_port_in(port, UART_IIR);
        serial_port_out(port, UART_IER, 0);

        if (port->irqflags & IRQF_SHARED)
            enable_irq(port->irq);
        spin_unlock_irqrestore(&port->lock, flags);

        /*
         * If the interrupt is not reasserted, or we otherwise
         * don't trust the iir, setup a timer to kick the UART
         * on a regular basis.
         */
        if ((!(iir1 & UART_IIR_NO_INT) && (iir & UART_IIR_NO_INT)) ||
            up->port.flags & UPF_BUG_THRE) {
            up->bugs |= UART_BUG_THRE;
            pr_debug("ttyS%d - using backup timer\n",
                 serial_index(port));
        }
    }

    /*
     * The above check will only give an accurate result the first time
     * the port is opened so this value needs to be preserved.
     */
    if (up->bugs & UART_BUG_THRE) {
        up->timer.function = serial8250_backup_timeout;
        up->timer.data = (unsigned long)up;
        mod_timer(&up->timer, jiffies +
            uart_poll_timeout(port) + HZ / 5);
    }

    /*
     * If the "interrupt" for this port doesn't correspond with any
     * hardware interrupt, we use a timer-based system.  The original
     * driver used to do this with IRQ0.
     */
    if (!port->irq) {
        up->timer.data = (unsigned long)up;
        mod_timer(&up->timer, jiffies + uart_poll_timeout(port));
    } else {
        retval = serial_link_irq_chain(up);
        if (retval)
            return retval;
    }

    /*
     * Now, initialize the UART
     */
    serial_port_out(port, UART_LCR, UART_LCR_WLEN8);

    spin_lock_irqsave(&port->lock, flags);
    if (up->port.flags & UPF_FOURPORT) {
        if (!up->port.irq)
            up->port.mctrl |= TIOCM_OUT1;
    } else
        /*
         * Most PC uarts need OUT2 raised to enable interrupts.
         */
        if (port->irq)
            up->port.mctrl |= TIOCM_OUT2;

    serial8250_set_mctrl(port, port->mctrl);

    /* Serial over Lan (SoL) hack:
       Intel 8257x Gigabit ethernet chips have a
       16550 emulation, to be used for Serial Over Lan.
       Those chips take a longer time than a normal
       serial device to signalize that a transmission
       data was queued. Due to that, the above test generally
       fails. One solution would be to delay the reading of
       iir. However, this is not reliable, since the timeout
       is variable. So, let's just don't test if we receive
       TX irq. This way, we'll never enable UART_BUG_TXEN.
     */
    if (skip_txen_test || up->port.flags & UPF_NO_TXEN_TEST)
        goto dont_test_tx_en;

    /*
     * Do a quick test to see if we receive an
     * interrupt when we enable the TX irq.
     */
    serial_port_out(port, UART_IER, UART_IER_THRI);
    lsr = serial_port_in(port, UART_LSR);
    iir = serial_port_in(port, UART_IIR);
    serial_port_out(port, UART_IER, 0);

    if (lsr & UART_LSR_TEMT && iir & UART_IIR_NO_INT) {
        if (!(up->bugs & UART_BUG_TXEN)) {
            up->bugs |= UART_BUG_TXEN;
            pr_debug("ttyS%d - enabling bad tx status workarounds\n",
                 serial_index(port));
        }
    } else {
        up->bugs &= ~UART_BUG_TXEN;
    }

dont_test_tx_en:
    spin_unlock_irqrestore(&port->lock, flags);

    /*
     * Clear the interrupt registers again for luck, and clear the
     * saved flags to avoid getting false values from polling
     * routines or the previous session.
     */
    serial_port_in(port, UART_LSR);
    serial_port_in(port, UART_RX);
    serial_port_in(port, UART_IIR);
    serial_port_in(port, UART_MSR);
    up->lsr_saved_flags = 0;
    up->msr_saved_flags = 0;

    /*
     * Request DMA channels for both RX and TX.
     */
    if (up->dma) {
        retval = serial8250_request_dma(up);
        if (retval) {
            pr_warn_ratelimited("ttyS%d - failed to request DMA\n",
                        serial_index(port));
            up->dma = NULL;
        }
    }

    /*
     * Finally, enable interrupts.  Note: Modem status interrupts
     * are set via set_termios(), which will be occurring imminently
     * anyway, so we don't enable them here.
     */
    up->ier = UART_IER_RLSI | UART_IER_RDI;
    serial_port_out(port, UART_IER, up->ier);

    if (port->flags & UPF_FOURPORT) {
        unsigned int icp;
        /*
         * Enable interrupts on the AST Fourport board
         */
        icp = (port->iobase & 0xfe0) | 0x01f;
        outb_p(0x80, icp);
        inb_p(icp);
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/dongkun152/article/details/81279378
今日推荐