Linux Uart Console Driver (2)--ttySn设备节点的操作

本文详细解析了 Linux 中 UART 控制台驱动的工作原理,包括 tty 设备节点的注册与操作、文件操作结构的定义及实现流程。重点介绍了 tty_open 函数内部的流程和 tty 结构体的创建过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;
}
<think>好的,我现在需要帮助用户理解在system-user.dts文件中配置&uart0和&sdhci0节点的作用。首先,我应该回忆一下设备树(Device Tree)在嵌入式系统中的作用,尤其是在U-Boot和Linux中的使用。 用户提供的代码片段中有两个节点uart0和sdhci0,它们都被赋予了u-boot,dm-pre-reloc属性和status设置为okay。我需要解释这两个属性的具体作用,以及它们在系统启动过程中的影响。 首先,u-boot,dm-pre-reloc属性。我记得在U-Boot中,设备树用于描述硬件信息,而dm-pre-reloc可能与设备模型(Driver Model)有关。这个属性可能指示U-Boot在重定位前保留该设备的驱动,确保在早期初始化阶段可用。需要确认这个属性是否用于让U-Boot在重定位之前初始化这些设备,比如用于调试串口或SD卡访问,这对于需要早期初始化的外设很重要。 然后是status = "okay",这应该表示启用该设备节点,让U-Boot或Linux内核知道这个设备存在并需要被初始化。在设备树中,status属性控制设备是否被激活,设置为okay意味着启用,disabled则是禁用。 接下来,这两个节点被放在system-user.dts文件中。这个文件通常是用于用户自定义设备树配置的地方,允许覆盖或补充默认的设备树设置。所以在这里配置uart0和sdhci0可能是为了在系统启动早期阶段使用这些设备,比如通过串口输出调试信息,或者从SD卡加载系统镜像。 需要进一步考虑这些配置的实际应用场景。例如,uart0通常是串口通信,用于调试输出;sdhci0可能连接SD卡控制器,用于启动时读取内核或文件系统。启用dm-pre-reloc确保U-Boot在重定位前就能使用这些设备,这对需要早期初始化的外设至关重要。 另外,要确保解释清楚每个属性的作用,以及它们如何影响启动流程。用户可能想知道为什么需要特别配置这些节点,而不是使用默认设置。可能的答案是,默认情况下某些设备可能在U-Boot阶段未被启用,或者需要提前初始化以支持特定功能。 还要注意是否正确处理了设备树语法,例如覆盖原有节点的方式是否正确使用&符号引用节点标签,然后添加或修改属性。用户提供的代码片段正确使用了这种语法,覆盖了原设备树中的uart0和sdhci0节点,添加了所需的属性。 总结来说,这两个配置的作用是确保U-Boot在早期阶段初始化和使用uart0和sdhci0设备,以便进行调试和存储访问,同时启用这些设备操作系统继续使用。</think>在嵌入式开发中,`system-user.dts` 文件用于对设备树进行用户级定制。您提供的配置片段属于**设备树覆盖(Device Tree Overlay)**,主要作用是为特定硬件节点添加U-Boot和Linux内核的初始化行为。以下是逐层解析: --- ### 一、节点配置解析 #### 1. `&uart0` 节点 ```dts &uart0 { u-boot,dm-pre-reloc; // 关键属性 status = "okay"; // 启用设备 }; ``` - **`u-boot,dm-pre-reloc`** 指示U-Boot在**重定位阶段前**初始化该设备- 重定位(Relocation)是U-Boot将自身从加载地址复制到链接地址的过程。 -设备(如调试串口)需在重定位前使用(例如输出早期日志),必须添加此属性。 - **`status = "okay"`** 显式启用UART0控制器,确保其驱动在U-Boot和Linux中均被加载。 #### 2. `&sdhci0` 节点 ```dts &sdhci0 { u-boot,dm-pre-reloc; status = "okay"; }; ``` - **`u-boot,dm-pre-reloc`** 同理,要求U-Boot在重定位前初始化SD/MMC控制器。 - 若系统需要从SD卡加载内核或文件系统,此属性确保U-Boot能提前访问存储设备- **`status = "okay"`** 启用SDHCI0控制器,使系统能识别SD卡插槽或eMMC存储。 --- ### 二、配置的实际作用 #### 1. **U-Boot阶段** - **调试支持** 启用`uart0`后,可通过串口实时输出U-Boot启动日志,对移植和调试至关重要。 - **存储访问** 启用`sdhci0`后,U-Boot可从SD卡读取内核镜像(`Image`)设备(`.dtb`)或文件系统,支持如`load mmc 0:1 $kernel_addr_r /boot/image`等操作。 #### 2. **Linux内核阶段** - **设备继承** U-Boot初始化过的设备状态(如时钟、引脚复用)会传递到内核,避免重复配置。 - **驱动匹配** `status = "okay"` 确保内核根据设备树自动加载对应驱动,无需手动`insmod`。 --- ### 三、典型应用场景 1. **从SD卡启动系统** 需提前初始化SD控制器以加载内核。 2. **串口调试** 在U-Boot阶段输出日志,或在Linux中作为控制台(`console=ttyS0`)。 3. **保留关键设备** 避免因U-Boot重定位导致设备失联,确保初始化顺序正确。 --- ### 四、验证配置生效 1. **检查U-Boot日志** 若看到串口输出`UART0 enabled`或`MMC: sdhci@xxx`,说明配置成功。 2. **Linux设备树信息** 在Linux中执行: ```bash ls /sys/bus/platform/devices/ # 查看已注册设备 dmesg | grep sdhci # 检索驱动加载记录 ``` --- ### 总结 通过在`system-user.dts`中添加这两段配置,您确保了: - UART0和SDHCI0在U-Boot早期阶段可用 - 设备Linux中自动启用 - 支持存储启动和调试输出两大核心功能 此配置常见于需要定制启动流程的嵌入式场景,如开发板移植或量产系统优化。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值