UART 有三条线,分别是 Rx,Tx 和 GND
数据发送接收步骤:
1.双方约定波特率
2.拉低(从高电平) Tx 引脚维持 1bit 时间
3.接收端在低电平开始处计时
4.发送端根据数据驱动 Tx 引脚电平
5.接收端 1.5bit 时间后读取引脚状态(前面有一个开始位)
6.bit8 是可选的校验位(可分为奇校验和偶校验)
7.发送停止位(可以约定停止位占据多少时间)
驱动注册
/ drivers / tty / serial / imx.c
先看 init 函数,其注册了imx_uart_uart_driver 和一个平台驱动
static int __init imx_uart_init(void)
{int ret = uart_register_driver(&imx_uart_uart_driver);if (ret)return ret;ret = platform_driver_register(&imx_uart_platform_driver);if (ret != 0)uart_unregister_driver(&imx_uart_uart_driver);return ret;
}
static struct uart_driver imx_uart_uart_driver = {.owner = THIS_MODULE,.driver_name = DRIVER_NAME,.dev_name = DEV_NAME,.major = SERIAL_IMX_MAJOR,.minor = MINOR_START,.nr = ARRAY_SIZE(imx_uart_ports),.cons = IMX_CONSOLE,
};
static struct platform_driver imx_uart_platform_driver = {.probe = imx_uart_probe,.remove = imx_uart_remove,.driver = {.name = "imx-uart",.of_match_table = imx_uart_dt_ids,.pm = &imx_uart_pm_ops,},
};
看看这个 probe 函数,这里面放入了操作函数结构体imx_uart_pops
static int imx_uart_probe(struct platform_device *pdev)
{struct device_node *np = pdev->dev.of_node;struct imx_port *sport;void __iomem *base;u32 dma_buf_conf[2];int ret = 0;u32 ucr1;struct resource *res;int txirq, rxirq, rtsirq;sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);if (!sport)return -ENOMEM;sport->devdata = of_device_get_match_data(&pdev->dev);ret = of_alias_get_id(np, "serial");if (ret < 0) {dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret);return ret;}sport->port.line = ret;if (of_get_property(np, "uart-has-rtscts", NULL) ||of_get_property(np, "fsl,uart-has-rtscts", NULL) /* deprecated */)sport->have_rtscts = 1;if (of_get_property(np, "fsl,dte-mode", NULL))sport->dte_mode = 1;if (of_get_property(np, "rts-gpios", NULL))sport->have_rtsgpio = 1;if (of_get_property(np, "fsl,inverted-tx", NULL))sport->inverted_tx = 1;if (of_get_property(np, "fsl,inverted-rx", NULL))sport->inverted_rx = 1;if (!of_property_read_u32_array(np, "fsl,dma-info", dma_buf_conf, 2)) {sport->rx_period_length = dma_buf_conf[0];sport->rx_periods = dma_buf_conf[1];} else {sport->rx_period_length = RX_DMA_PERIOD_LEN;sport->rx_periods = RX_DMA_PERIODS;}if (sport->port.line >= ARRAY_SIZE(imx_uart_ports)) {dev_err(&pdev->dev, "serial%d out of range\n",sport->port.line);return -EINVAL;}res = platform_get_resource(pdev, IORESOURCE_MEM, 0);base = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(base))return PTR_ERR(base);rxirq = platform_get_irq(pdev, 0);if (rxirq < 0)return rxirq;txirq = platform_get_irq_optional(pdev, 1);rtsirq = platform_get_irq_optional(pdev, 2);sport->port.dev = &pdev->dev;sport->port.mapbase = res->start;sport->port.membase = base;sport->port.type = PORT_IMX;sport->port.iotype = UPIO_MEM;sport->port.irq = rxirq;sport->port.fifosize = 32;sport->port.has_sysrq = IS_ENABLED(CONFIG_SERIAL_IMX_CONSOLE);sport->port.ops = &imx_uart_pops;sport->port.rs485_config = imx_uart_rs485_config;sport->port.flags = UPF_BOOT_AUTOCONF;timer_setup(&sport->timer, imx_uart_timeout, 0);sport->gpios = mctrl_gpio_init(&sport->port, 0);if (IS_ERR(sport->gpios))return PTR_ERR(sport->gpios);sport->clk_ipg = devm_clk_get(&pdev->dev, "ipg");if (IS_ERR(sport->clk_ipg)) {ret = PTR_ERR(sport->clk_ipg);dev_err(&pdev->dev, "failed to get ipg clk: %d\n", ret);return ret;}sport->clk_per = devm_clk_get(&pdev->dev, "per");if (IS_ERR(sport->clk_per)) {ret = PTR_ERR(sport->clk_per);dev_err(&pdev->dev, "failed to get per clk: %d\n", ret);return ret;}sport->port.uartclk = clk_get_rate(sport->clk_per);/* For register access, we only need to enable the ipg clock. */ret = clk_prepare_enable(sport->clk_ipg);if (ret) {dev_err(&pdev->dev, "failed to enable per clk: %d\n", ret);return ret;}/* initialize shadow register values */sport->ucr1 = readl(sport->port.membase + UCR1);sport->ucr2 = readl(sport->port.membase + UCR2);sport->ucr3 = readl(sport->port.membase + UCR3);sport->ucr4 = readl(sport->port.membase + UCR4);sport->ufcr = readl(sport->port.membase + UFCR);ret = uart_get_rs485_mode(&sport->port);if (ret) {clk_disable_unprepare(sport->clk_ipg);return ret;}if (sport->port.rs485.flags & SER_RS485_ENABLED &&(!sport->have_rtscts && !sport->have_rtsgpio))dev_err(&pdev->dev, "no RTS control, disabling rs485\n");/** If using the i.MX UART RTS/CTS control then the RTS (CTS_B)* signal cannot be set low during transmission in case the* receiver is off (limitation of the i.MX UART IP).*/if (sport->port.rs485.flags & SER_RS485_ENABLED &&sport->have_rtscts && !sport->have_rtsgpio &&(!(sport->port.rs485.flags & SER_RS485_RTS_ON_SEND) &&!(sport->port.rs485.flags & SER_RS485_RX_DURING_TX)))dev_err(&pdev->dev,"low-active RTS not possible when receiver is off, enabling receiver\n");imx_uart_rs485_config(&sport->port, &sport->port.rs485);/* Disable interrupts before requesting them */ucr1 = imx_uart_readl(sport, UCR1);ucr1 &= ~(UCR1_ADEN | UCR1_TRDYEN | UCR1_IDEN | UCR1_RRDYEN | UCR1_RTSDEN);imx_uart_writel(sport, ucr1, UCR1);if (!imx_uart_is_imx1(sport) && sport->dte_mode) {/** The DCEDTE bit changes the direction of DSR, DCD, DTR and RI* and influences if UCR3_RI and UCR3_DCD changes the level of RI* and DCD (when they are outputs) or enables the respective* irqs. So set this bit early, i.e. before requesting irqs.*/u32 ufcr = imx_uart_readl(sport, UFCR);if (!(ufcr & UFCR_DCEDTE))imx_uart_writel(sport, ufcr | UFCR_DCEDTE, UFCR);/** Disable UCR3_RI and UCR3_DCD irqs. They are also not* enabled later because they cannot be cleared* (confirmed on i.MX25) which makes them unusable.*/imx_uart_writel(sport,IMX21_UCR3_RXDMUXSEL | UCR3_ADNIMP | UCR3_DSR,UCR3);} else {u32 ucr3 = UCR3_DSR;u32 ufcr = imx_uart_readl(sport, UFCR);if (ufcr & UFCR_DCEDTE)imx_uart_writel(sport, ufcr & ~UFCR_DCEDTE, UFCR);if (!imx_uart_is_imx1(sport))ucr3 |= IMX21_UCR3_RXDMUXSEL | UCR3_ADNIMP;imx_uart_writel(sport, ucr3, UCR3);}clk_disable_unprepare(sport->clk_ipg);hrtimer_init(&sport->trigger_start_tx, CLOCK_MONOTONIC, HRTIMER_MODE_REL);hrtimer_init(&sport->trigger_stop_tx, CLOCK_MONOTONIC, HRTIMER_MODE_REL);sport->trigger_start_tx.function = imx_trigger_start_tx;sport->trigger_stop_tx.function = imx_trigger_stop_tx;/** Allocate the IRQ(s) i.MX1 has three interrupts whereas later* chips only have one interrupt.*/if (txirq > 0) {ret = devm_request_irq(&pdev->dev, rxirq, imx_uart_rxint, 0,dev_name(&pdev->dev), sport);if (ret) {dev_err(&pdev->dev, "failed to request rx irq: %d\n",ret);return ret;}ret = devm_request_irq(&pdev->dev, txirq, imx_uart_txint, 0,dev_name(&pdev->dev), sport);if (ret) {dev_err(&pdev->dev, "failed to request tx irq: %d\n",ret);return ret;}ret = devm_request_irq(&pdev->dev, rtsirq, imx_uart_rtsint, 0,dev_name(&pdev->dev), sport);if (ret) {dev_err(&pdev->dev, "failed to request rts irq: %d\n",ret);return ret;}} else {ret = devm_request_irq(&pdev->dev, rxirq, imx_uart_int, 0,dev_name(&pdev->dev), sport);if (ret) {dev_err(&pdev->dev, "failed to request irq: %d\n", ret);return ret;}}imx_uart_ports[sport->port.line] = sport;platform_set_drvdata(pdev, sport);return uart_add_one_port(&imx_uart_uart_driver, &sport->port);
}
static const struct uart_ops imx_uart_pops = {.tx_empty = imx_uart_tx_empty,.set_mctrl = imx_uart_set_mctrl,.get_mctrl = imx_uart_get_mctrl,.stop_tx = imx_uart_stop_tx,.start_tx = imx_uart_start_tx,.stop_rx = imx_uart_stop_rx,.enable_ms = imx_uart_enable_ms,.break_ctl = imx_uart_break_ctl,.startup = imx_uart_startup,.shutdown = imx_uart_shutdown,.flush_buffer = imx_uart_flush_buffer,.set_termios = imx_uart_set_termios,.type = imx_uart_type,.config_port = imx_uart_config_port,.verify_port = imx_uart_verify_port,
#if defined(CONFIG_CONSOLE_POLL).poll_init = imx_uart_poll_init,.poll_get_char = imx_uart_poll_get_char,.poll_put_char = imx_uart_poll_put_char,
#endif
};
来看看前面注册imx_uart_uart_driver 的过程,这里初始化并注册了tty_driver,注意这里把前面那个uart_ops 给了tty_driver,这里还给驱动分配了一个state
/*** uart_register_driver - register a driver with the uart core layer* @drv: low level driver structure** Register a uart driver with the core driver. We in turn register* with the tty layer, and initialise the core driver per-port state.** We have a proc file in /proc/tty/driver which is named after the* normal driver.** drv->port should be NULL, and the per-port structures should be* registered using uart_add_one_port after this call has succeeded.*/
int uart_register_driver(struct uart_driver *drv)
{struct tty_driver *normal;int i, retval = -ENOMEM;BUG_ON(drv->state);/** Maybe we should be using a slab cache for this, especially if* we have a large number of ports to handle.*/drv->state = kcalloc(drv->nr, sizeof(struct uart_state), GFP_KERNEL);if (!drv->state)goto out;normal = tty_alloc_driver(drv->nr, TTY_DRIVER_REAL_RAW |TTY_DRIVER_DYNAMIC_DEV);if (IS_ERR(normal)) {retval = PTR_ERR(normal);goto out_kfree;}drv->tty_driver = normal;normal->driver_name = drv->driver_name;normal->name = drv->dev_name;normal->major = drv->major;normal->minor_start = drv->minor;normal->type = TTY_DRIVER_TYPE_SERIAL;normal->subtype = SERIAL_TYPE_NORMAL;normal->init_termios = tty_std_termios;normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;normal->driver_state = drv;tty_set_operations(normal, &uart_ops);/** Initialise the UART state(s).*/for (i = 0; i < drv->nr; i++) {struct uart_state *state = drv->state + i;struct tty_port *port = &state->port;tty_port_init(port);port->ops = &uart_port_ops;}retval = tty_register_driver(normal);if (retval >= 0)return retval;for (i = 0; i < drv->nr; i++)tty_port_destroy(&drv->state[i].port);tty_driver_kref_put(normal);
out_kfree:kfree(drv->state);
out:return retval;
}
EXPORT_SYMBOL(uart_register_driver);
来看看tty_alloc_driver
/* Use TTY_DRIVER_* flags below */
#define tty_alloc_driver(lines, flags) \__tty_alloc_driver(lines, THIS_MODULE, flags)
/ drivers / tty / tty_io.c
这里分配了 cdev,数量等于 port 数量
每个串口都有 ports ttys termios cdevs
/*** __tty_alloc_driver -- allocate tty driver* @lines: count of lines this driver can handle at most* @owner: module which is responsible for this driver* @flags: some of %TTY_DRIVER_ flags, will be set in driver->flags** This should not be called directly, some of the provided macros should be* used instead. Use IS_ERR() and friends on @retval.*/
struct tty_driver *__tty_alloc_driver(unsigned int lines, struct module *owner,unsigned long flags)
{struct tty_driver *driver;unsigned int cdevs = 1;int err;if (!lines || (flags & TTY_DRIVER_UNNUMBERED_NODE && lines > 1))return ERR_PTR(-EINVAL);driver = kzalloc(sizeof(*driver), GFP_KERNEL);if (!driver)return ERR_PTR(-ENOMEM);kref_init(&driver->kref);driver->magic = TTY_DRIVER_MAGIC;driver->num = lines;driver->owner = owner;driver->flags = flags;if (!(flags & TTY_DRIVER_DEVPTS_MEM)) {driver->ttys = kcalloc(lines, sizeof(*driver->ttys),GFP_KERNEL);driver->termios = kcalloc(lines, sizeof(*driver->termios),GFP_KERNEL);if (!driver->ttys || !driver->termios) {err = -ENOMEM;goto err_free_all;}}if (!(flags & TTY_DRIVER_DYNAMIC_ALLOC)) {driver->ports = kcalloc(lines, sizeof(*driver->ports),GFP_KERNEL);if (!driver->ports) {err = -ENOMEM;goto err_free_all;}cdevs = lines;}driver->cdevs = kcalloc(cdevs, sizeof(*driver->cdevs), GFP_KERNEL);if (!driver->cdevs) {err = -ENOMEM;goto err_free_all;}return driver;
err_free_all:kfree(driver->ports);kfree(driver->ttys);kfree(driver->termios);kfree(driver->cdevs);kfree(driver);return ERR_PTR(err);
}
EXPORT_SYMBOL(__tty_alloc_driver);
看看 probe 函数的最后一步uart_add_one_port
/ drivers / tty / serial / serial_core.c
/*** uart_add_one_port - attach a driver-defined port structure* @drv: pointer to the uart low level driver structure for this port* @uport: uart port structure to use for this port.** Context: task context, might sleep** This allows the driver to register its own uart_port structure* with the core driver. The main purpose is to allow the low* level uart drivers to expand uart_port, rather than having yet* more levels of structures.*/
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{struct uart_state *state;struct tty_port *port;int ret = 0;struct device *tty_dev;int num_groups;if (uport->line >= drv->nr)return -EINVAL;state = drv->state + uport->line;port = &state->port;mutex_lock(&port_mutex);mutex_lock(&port->mutex);if (state->uart_port) {ret = -EINVAL;goto out;}/* Link the port to the driver state table and vice versa */atomic_set(&state->refcount, 1);init_waitqueue_head(&state->remove_wait);state->uart_port = uport;uport->state = state;state->pm_state = UART_PM_STATE_UNDEFINED;uport->cons = drv->cons;uport->minor = drv->tty_driver->minor_start + uport->line;uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name,drv->tty_driver->name_base + uport->line);if (!uport->name) {ret = -ENOMEM;goto out;}/** If this port is in use as a console then the spinlock is already* initialised.*/if (!uart_console_enabled(uport))uart_port_spin_lock_init(uport);if (uport->cons && uport->dev)of_console_check(uport->dev->of_node, uport->cons->name, uport->line);tty_port_link_device(port, drv->tty_driver, uport->line);uart_configure_port(drv, state, uport);port->console = uart_console(uport);num_groups = 2;if (uport->attr_group)num_groups++;uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),GFP_KERNEL);if (!uport->tty_groups) {ret = -ENOMEM;goto out;}uport->tty_groups[0] = &tty_dev_attr_group;if (uport->attr_group)uport->tty_groups[1] = uport->attr_group;/** Register the port whether it's detected or not. This allows* setserial to be used to alter this port's parameters.*/tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,uport->line, uport->dev, port, uport->tty_groups);if (!IS_ERR(tty_dev)) {device_set_wakeup_capable(tty_dev, 1);} else {dev_err(uport->dev, "Cannot register tty device on line %d\n",uport->line);}/** Ensure UPF_DEAD is not set.*/uport->flags &= ~UPF_DEAD;out:mutex_unlock(&port->mutex);mutex_unlock(&port_mutex);return ret;
}
EXPORT_SYMBOL(uart_add_one_port);
/ drivers / tty / tty_port.c
/*** tty_port_register_device_attr_serdev - register tty or serdev device* @port: tty_port of the device* @driver: tty_driver for this device* @index: index of the tty* @device: parent if exists, otherwise NULL* @drvdata: driver data for the device* @attr_grp: attribute group for the device** Register a serdev or tty device depending on if the parent device has any* defined serdev clients or not.*/
struct device *tty_port_register_device_attr_serdev(struct tty_port *port,struct tty_driver *driver, unsigned index,struct device *device, void *drvdata,const struct attribute_group **attr_grp)
{struct device *dev;tty_port_link_device(port, driver, index);dev = serdev_tty_port_register(port, device, driver, index);if (PTR_ERR(dev) != -ENODEV) {/* Skip creating cdev if we registered a serdev device */return dev;}return tty_register_device_attr(driver, index, device, drvdata,attr_grp);
}
EXPORT_SYMBOL_GPL(tty_port_register_device_attr_serdev);
/*** tty_port_link_device - link tty and tty_port* @port: tty_port of the device* @driver: tty_driver for this device* @index: index of the tty** Provide the tty layer with a link from a tty (specified by @index) to a* tty_port (@port). Use this only if neither tty_port_register_device() nor* tty_port_install() is used in the driver. If used, this has to be called* before tty_register_driver().*/
void tty_port_link_device(struct tty_port *port,struct tty_driver *driver, unsigned index)
{if (WARN_ON(index >= driver->num))return;driver->ports[index] = port;
}
EXPORT_SYMBOL_GPL(tty_port_link_device);
/ drivers / tty / tty_io.c
这里调用了tty_cdev_add
/*** tty_register_device_attr - register a tty device* @driver: the tty driver that describes the tty device* @index: the index in the tty driver for this tty device* @device: a struct device that is associated with this tty device.* This field is optional, if there is no known struct device* for this tty device it can be set to %NULL safely.* @drvdata: Driver data to be set to device.* @attr_grp: Attribute group to be set on device.** This call is required to be made to register an individual tty device if the* tty driver's flags have the %TTY_DRIVER_DYNAMIC_DEV bit set. If that bit is* not set, this function should not be called by a tty driver.** Locking: ??** Return: A pointer to the struct device for this tty device (or* ERR_PTR(-EFOO) on error).*/
struct device *tty_register_device_attr(struct tty_driver *driver,unsigned index, struct device *device,void *drvdata,const struct attribute_group **attr_grp)
{char name[64];dev_t devt = MKDEV(driver->major, driver->minor_start) + index;struct ktermios *tp;struct device *dev;int retval;if (index >= driver->num) {pr_err("%s: Attempt to register invalid tty line number (%d)\n",driver->name, index);return ERR_PTR(-EINVAL);}if (driver->type == TTY_DRIVER_TYPE_PTY)pty_line_name(driver, index, name);elsetty_line_name(driver, index, name);dev = kzalloc(sizeof(*dev), GFP_KERNEL);if (!dev)return ERR_PTR(-ENOMEM);dev->devt = devt;dev->class = tty_class;dev->parent = device;dev->release = tty_device_create_release;dev_set_name(dev, "%s", name);dev->groups = attr_grp;dev_set_drvdata(dev, drvdata);dev_set_uevent_suppress(dev, 1);retval = device_register(dev);if (retval)goto err_put;if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {/** Free any saved termios data so that the termios state is* reset when reusing a minor number.*/tp = driver->termios[index];if (tp) {driver->termios[index] = NULL;kfree(tp);}retval = tty_cdev_add(driver, devt, index, 1);if (retval)goto err_del;}dev_set_uevent_suppress(dev, 0);kobject_uevent(&dev->kobj, KOBJ_ADD);return dev;err_del:device_del(dev);
err_put:put_device(dev);return ERR_PTR(retval);
}
EXPORT_SYMBOL_GPL(tty_register_device_attr);
这个函数中添加了cdevs 的操作函数tty_fops
static int tty_cdev_add(struct tty_driver *driver, dev_t dev,unsigned int index, unsigned int count)
{int err;/* init here, since reused cdevs cause crashes */driver->cdevs[index] = cdev_alloc();if (!driver->cdevs[index])return -ENOMEM;driver->cdevs[index]->ops = &tty_fops;driver->cdevs[index]->owner = driver->owner;err = cdev_add(driver->cdevs[index], dev, count);if (err)kobject_put(&driver->cdevs[index]->kobj);return err;
}
static const struct file_operations tty_fops = {.llseek = no_llseek,.read_iter = tty_read,.write_iter = tty_write,.splice_read = generic_file_splice_read,.splice_write = iter_file_splice_write,.poll = tty_poll,.unlocked_ioctl = tty_ioctl,.compat_ioctl = tty_compat_ioctl,.open = tty_open,.release = tty_release,.fasync = tty_fasync,.show_fdinfo = tty_show_fdinfo,
};
对外的接口就是这些函数了,这里分析一下 open 的流程,其他流程都是类似的
open
/ drivers / tty / tty_io.c
/*** 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 the 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;dev_t device = inode->i_rdev;unsigned saved_flags = filp->f_flags;nonseekable_open(inode, filp);retry_open:retval = tty_alloc_file(filp);if (retval)return -ENOMEM;tty = tty_open_current_tty(device, filp);if (!tty)tty = tty_open_by_driver(device, filp);if (IS_ERR(tty)) {tty_free_file(filp);retval = PTR_ERR(tty);if (retval != -EAGAIN || signal_pending(current))return retval;schedule();goto retry_open;}tty_add_file(tty, filp);check_tty_count(tty, __func__);tty_debug_hangup(tty, "opening (count=%d)\n", tty->count);if (tty->ops->open)retval = tty->ops->open(tty, filp);elseretval = -ENODEV;filp->f_flags = saved_flags;if (retval) {tty_debug_hangup(tty, "open error %d, releasing\n", retval);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 (tty_hung_up_p(filp))filp->f_op = &tty_fops;goto retry_open;}clear_bit(TTY_HUPPED, &tty->flags);noctty = (filp->f_flags & O_NOCTTY) ||(IS_ENABLED(CONFIG_VT) && device == MKDEV(TTY_MAJOR, 0)) ||device == MKDEV(TTYAUX_MAJOR, 1) ||(tty->driver->type == TTY_DRIVER_TYPE_PTY &&tty->driver->subtype == PTY_TYPE_MASTER);if (!noctty)tty_open_proc_set_tty(filp, tty);tty_unlock(tty);return 0;
}
这里从 tty 驱动中获取tty_struct,tty_struct 代表了一个 tty 设备
static struct tty_struct *tty_open_by_driver(dev_t device,struct file *filp)
{struct tty_struct *tty;struct tty_driver *driver = NULL;int index = -1;int retval;mutex_lock(&tty_mutex);driver = tty_lookup_driver(device, filp, &index);if (IS_ERR(driver)) {mutex_unlock(&tty_mutex);return ERR_CAST(driver);}/* check whether we're reopening an existing tty */tty = tty_driver_lookup_tty(driver, filp, index);if (IS_ERR(tty)) {mutex_unlock(&tty_mutex);goto out;}if (tty) {if (tty_port_kopened(tty->port)) {tty_kref_put(tty);mutex_unlock(&tty_mutex);tty = ERR_PTR(-EBUSY);goto out;}mutex_unlock(&tty_mutex);retval = tty_lock_interruptible(tty);tty_kref_put(tty); /* drop kref from tty_driver_lookup_tty() */if (retval) {if (retval == -EINTR)retval = -ERESTARTSYS;tty = ERR_PTR(retval);goto out;}retval = tty_reopen(tty);if (retval < 0) {tty_unlock(tty);tty = ERR_PTR(retval);}} else { /* Returns with the tty_lock held for now */tty = tty_init_dev(driver, index);mutex_unlock(&tty_mutex);}
out:tty_driver_kref_put(driver);return tty;
}
回到tty_open 的处理,里面进行了tty->ops->open 这样的操作,这个是之前在核心层注册的
static const struct tty_operations uart_ops = {.install = uart_install,.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_show = uart_proc_show,
#endif.tiocmget = uart_tiocmget,.tiocmset = uart_tiocmset,.set_serial = uart_set_info_user,.get_serial = uart_get_info_user,.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
};
static int uart_open(struct tty_struct *tty, struct file *filp)
{struct uart_state *state = tty->driver_data;int retval;retval = tty_port_open(&state->port, tty, filp);if (retval > 0)retval = 0;return retval;
}
这里面就是调用的激活函数
int tty_port_open(struct tty_port *port, struct tty_struct *tty,struct file *filp)
{spin_lock_irq(&port->lock);++port->count;spin_unlock_irq(&port->lock);tty_port_tty_set(port, tty);/** Do the device-specific open only if the hardware isn't* already initialized. Serialize open and shutdown using the* port mutex.*/mutex_lock(&port->mutex);if (!tty_port_initialized(port)) {clear_bit(TTY_IO_ERROR, &tty->flags);if (port->ops->activate) {int retval = port->ops->activate(port, tty);if (retval) {mutex_unlock(&port->mutex);return retval;}}tty_port_set_initialized(port, 1);}mutex_unlock(&port->mutex);return tty_port_block_til_ready(port, tty, filp);
}
EXPORT_SYMBOL(tty_port_open);
/ drivers / tty / serial / serial_core.c
static int uart_port_activate(struct tty_port *port, struct tty_struct *tty)
{struct uart_state *state = container_of(port, struct uart_state, port);struct uart_port *uport;int ret;uport = uart_port_check(state);if (!uport || uport->flags & UPF_DEAD)return -ENXIO;/** Start up the serial port.*/ret = uart_startup(tty, state, 0);if (ret > 0)tty_port_set_active(port, 1);return ret;
}
static int uart_startup(struct tty_struct *tty, struct uart_state *state,int init_hw)
{struct tty_port *port = &state->port;int retval;if (tty_port_initialized(port))return 0;retval = uart_port_startup(tty, state, init_hw);if (retval)set_bit(TTY_IO_ERROR, &tty->flags);return retval;
}
这里面就调用到了前面给 port 注册的 ops 的函数了
static int uart_port_startup(struct tty_struct *tty, struct uart_state *state,int init_hw)
{struct uart_port *uport = uart_port_check(state);unsigned long flags;unsigned long page;int retval = 0;if (uport->type == PORT_UNKNOWN)return 1;/** Make sure the device is in D0 state.*/uart_change_pm(state, UART_PM_STATE_ON);/** Initialise and allocate the transmit and temporary* buffer.*/page = get_zeroed_page(GFP_KERNEL);if (!page)return -ENOMEM;uart_port_lock(state, flags);if (!state->xmit.buf) {state->xmit.buf = (unsigned char *) page;uart_circ_clear(&state->xmit);uart_port_unlock(uport, flags);} else {uart_port_unlock(uport, flags);/** Do not free() the page under the port lock, see* uart_shutdown().*/free_page(page);}retval = uport->ops->startup(uport);if (retval == 0) {if (uart_console(uport) && uport->cons->cflag) {tty->termios.c_cflag = uport->cons->cflag;tty->termios.c_ispeed = uport->cons->ispeed;tty->termios.c_ospeed = uport->cons->ospeed;uport->cons->cflag = 0;uport->cons->ispeed = 0;uport->cons->ospeed = 0;}/** Initialise the hardware port settings.*/uart_change_speed(tty, state, NULL);/** Setup the RTS and DTR signals once the* port is open and ready to respond.*/if (init_hw && C_BAUD(tty))uart_port_dtr_rts(uport, 1);}/** 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;
}
/ drivers / tty / serial / imx.c
static int imx_uart_startup(struct uart_port *port)
{struct imx_port *sport = (struct imx_port *)port;int retval, i;unsigned long flags;int dma_is_inited = 0;u32 ucr1, ucr2, ucr3, ucr4;retval = clk_prepare_enable(sport->clk_per);if (retval)return retval;retval = clk_prepare_enable(sport->clk_ipg);if (retval) {clk_disable_unprepare(sport->clk_per);return retval;}imx_uart_setup_ufcr(sport, TXTL_DEFAULT, RXTL_DEFAULT);/* disable the DREN bit (Data Ready interrupt enable) before* requesting IRQs*/ucr4 = imx_uart_readl(sport, UCR4);/* set the trigger level for CTS */ucr4 &= ~(UCR4_CTSTL_MASK << UCR4_CTSTL_SHF);ucr4 |= CTSTL << UCR4_CTSTL_SHF;imx_uart_writel(sport, ucr4 & ~UCR4_DREN, UCR4);/* Can we enable the DMA support? */if (!uart_console(port) && imx_uart_dma_init(sport) == 0)dma_is_inited = 1;spin_lock_irqsave(&sport->port.lock, flags);/* Reset fifo's and state machines */i = 100;ucr2 = imx_uart_readl(sport, UCR2);ucr2 &= ~UCR2_SRST;imx_uart_writel(sport, ucr2, UCR2);while (!(imx_uart_readl(sport, UCR2) & UCR2_SRST) && (--i > 0))udelay(1);/** Finally, clear and enable interrupts*/imx_uart_writel(sport, USR1_RTSD | USR1_DTRD, USR1);imx_uart_writel(sport, USR2_ORE, USR2);ucr1 = imx_uart_readl(sport, UCR1) & ~UCR1_RRDYEN;ucr1 |= UCR1_UARTEN;if (sport->have_rtscts)ucr1 |= UCR1_RTSDEN;imx_uart_writel(sport, ucr1, UCR1);ucr4 = imx_uart_readl(sport, UCR4) & ~(UCR4_OREN | UCR4_INVR);if (!dma_is_inited)ucr4 |= UCR4_OREN;if (sport->inverted_rx)ucr4 |= UCR4_INVR;imx_uart_writel(sport, ucr4, UCR4);ucr3 = imx_uart_readl(sport, UCR3) & ~UCR3_INVT;/** configure tx polarity before enabling tx*/if (sport->inverted_tx)ucr3 |= UCR3_INVT;if (!imx_uart_is_imx1(sport)) {ucr3 |= UCR3_DTRDEN | UCR3_RI | UCR3_DCD;if (sport->dte_mode)/* disable broken interrupts */ucr3 &= ~(UCR3_RI | UCR3_DCD);}imx_uart_writel(sport, ucr3, UCR3);ucr2 = imx_uart_readl(sport, UCR2) & ~UCR2_ATEN;ucr2 |= (UCR2_RXEN | UCR2_TXEN);if (!sport->have_rtscts)ucr2 |= UCR2_IRTS;/** make sure the edge sensitive RTS-irq is disabled,* we're using RTSD instead.*/if (!imx_uart_is_imx1(sport))ucr2 &= ~UCR2_RTSEN;imx_uart_writel(sport, ucr2, UCR2);/** Enable modem status interrupts*/imx_uart_enable_ms(&sport->port);if (dma_is_inited) {imx_uart_enable_dma(sport);imx_uart_start_rx_dma(sport);} else {ucr1 = imx_uart_readl(sport, UCR1);ucr1 |= UCR1_RRDYEN;imx_uart_writel(sport, ucr1, UCR1);ucr2 = imx_uart_readl(sport, UCR2);ucr2 |= UCR2_ATEN;imx_uart_writel(sport, ucr2, UCR2);}spin_unlock_irqrestore(&sport->port.lock, flags);return 0;
}