上节介绍了serial驱动核心提供的重要数据结构,这一节将介绍serial核心提供给驱动开发的核心函数uart_register_driver向内核注册serial驱动(具体操作就是向内核注册一个tty_driver)以及uart_unregister_driver注销serial驱动。uart_add_one_port用于为串口驱动添加一个串口端口(在总线型设备驱动中,通常用于探测函数probe中),uart_remove_one_port用于删除一个已经添加到驱动中的串口端口(通常在卸载函数中调用)。

 

//uart_register_driver就是初始化一个tty_driver并把其加入tty core层,并对uart_state做一些初始化,起始就是写一个tty类驱动。

/**
 * 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 = NULL;  //定义一个tty_driver驱动指针
 int i, retval;

 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 = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL); //为串口设备管理分配空间即uart_state数组
 retval = -ENOMEM;
 if (!drv->state)
  goto out;

 normal  = alloc_tty_driver(drv->nr);
 if (!normal)
  goto out;

 drv->tty_driver = normal;

 normal->owner  = drv->owner;
 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;  //tty设备类型
 normal->subtype  = SERIAL_TYPE_NORMAL;  //tty设备子类型
 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;

//TTY_DRIVER_DYNAMIC_DEV是不会在初始化的时候去注册device.也就是说在/dev/下没有动态生成结点

 normal->flags  = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
 normal->driver_state    = drv; //便于查找uart_driver
 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;

  state->close_delay     = 500; /* .5 seconds */
  state->closing_wait    = 30000; /* 30 seconds */

  mutex_init(&state->mutex);
 }

 retval = tty_register_driver(normal); //把normal加入到tty core中
 out:
 if (retval < 0) {
  put_tty_driver(normal);
  kfree(drv->state);
 }
 return retval;
}

 

//uart_unregister_driver完成与上面代码相反的操作

 /**
 * uart_unregister_driver - remove a driver from the uart core layer
 * @drv: low level driver structure
 *
 * Remove all references to a driver from the core driver.  The low
 * level driver must have removed all its ports via the
 * uart_remove_one_port() if it registered them with uart_add_one_port().
 * (ie, drv->port == NULL)
 */
void uart_unregister_driver(struct uart_driver *drv)
{
 struct tty_driver *p = drv->tty_driver;
 tty_unregister_driver(p);
 put_tty_driver(p);
 kfree(drv->state);
 drv->tty_driver = NULL;
}

 

//uart_add_one_port 向uart_driver加入一个可操作的端口,注意serial驱动的核心在端口的操作函数上

//即const struct uart_ops *ops的成员函数上,每个端口对应一个uart_state结构

 /**
 * uart_add_one_port - attach a driver-defined port structure
 * @drv: pointer to the uart low level driver structure for this port
 * @port: uart port structure to use for this port.
 *
 * 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 *port)
{
 struct uart_state *state;
 int ret = 0;
 struct device *tty_dev;

 BUG_ON(in_interrupt());  //函数不能在中断环境中调用

 if (port->line >= drv->nr)  //port->line 指在uart_state数组中的索引
  return -EINVAL;

 state = drv->state + port->line;

 mutex_lock(&port_mutex);
 mutex_lock(&state->mutex);
 if (state->port) {
  ret = -EINVAL;
  goto out;
 }

 state->port = port;
 state->pm_state = -1;

 port->cons = drv->cons;
 port->info = state->info;  //两者指向同一个uart_info结构

 /*
  * If this port is a console, then the spinlock is already
  * initialised.
  */
 if (!(uart_console(port) && (port->cons->flags & CON_ENABLED))) {
  spin_lock_init(&port->lock);
  lockdep_set_class(&port->lock, &port_lock_key);
 }

 uart_configure_port(drv, state, port); //配置端口 下面具体分析

 /*
  * Register the port whether it's detected or not.  This allows
  * setserial to be used to alter this ports parameters.
  */
 tty_dev = tty_register_device(drv->tty_driver, port->line, port->dev); //注册tty设备
 if (likely(!IS_ERR(tty_dev))) { //设置设备的唤醒状态
  device_init_wakeup(tty_dev, 1);
  device_set_wakeup_enable(tty_dev, 0);
 } else
  printk(KERN_ERR "Cannot register tty device on line %d/n",
         port->line);

 /*
  * Ensure UPF_DEAD is not set.
  */
 port->flags &= ~UPF_DEAD;

 out:
 mutex_unlock(&state->mutex);
 mutex_unlock(&port_mutex);

 return ret;
}

 

//端口为配置的情况下调用端口的自动配置函数,否则直接报告端口信息以及modem控制设置等操作

static void uart_configure_port(struct uart_driver *drv, struct uart_state *state,
      struct uart_port *port)
{
 unsigned int flags;

 /*
  * If there isn't a port here, don't do anything further.
  */
 if (!port->iobase && !port->mapbase && !port->membase) //设备不存在
  return;

 /*
  * Now do the auto configuration stuff.  Note that config_port
  * is expected to claim the resources and map the port for us.
  */
 flags = UART_CONFIG_TYPE;
 if (port->flags & UPF_AUTO_IRQ)
  flags |= UART_CONFIG_IRQ;
 if (port->flags & UPF_BOOT_AUTOCONF) {
  port->type = PORT_UNKNOWN;
  port->ops->config_port(port, flags); //调用设备的自动配置函数,在后面的serial驱动例子中可以看看具体看什么
 }

 if (port->type != PORT_UNKNOWN) {
  unsigned long flags;

  uart_report_port(drv, port);  //输出端口的相关信息

  /* Power up port for set_mctrl() */
  uart_change_pm(state, 0); //改变端口的电源状态

  /* 
   * Ensure that the modem control lines are de-activated.
   * keep the DTR setting that is set in uart_set_options()
   * We probably don't need a spinlock around this, but
   */
  spin_lock_irqsave(&port->lock, flags);
  port->ops->set_mctrl(port, port->mctrl & TIOCM_DTR); //设置串口modem控制
  spin_unlock_irqrestore(&port->lock, flags);

  /*
   * If this driver supports console, and it hasn't been
   * successfully registered yet, try to re-register it.
   * It may be that the port was not available.
   */
  if (port->cons && !(port->cons->flags & CON_ENABLED))
   register_console(port->cons);

  /*
   * Power down all ports by default, except the
   * console if we have one.
   */
  if (!uart_console(port))
   uart_change_pm(state, 3);
 }
}

 

//uart_remove_one_port完成uart_add_one_port相反操作,删除一个已加入的串口端口

 

/**
 * uart_remove_one_port - detach a driver defined port structure
 * @drv: pointer to the uart low level driver structure for this port
 * @port: uart port structure for this port
 *
 * This unhooks (and hangs up) the specified port structure from the
 * core driver.  No further calls will be made to the low-level code
 * for this port.
 */
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *port)
{
 struct uart_state *state = drv->state + port->line;
 struct uart_info *info;

 BUG_ON(in_interrupt());

 if (state->port != port)  //删除端口与驱动对应端口不匹配
  printk(KERN_ALERT "Removing wrong port: %p != %p/n",
   state->port, port);

 mutex_lock(&port_mutex);

 /*
  * Mark the port "dead" - this prevents any opens from
  * succeeding while we shut down the port.
  */
 mutex_lock(&state->mutex);
 port->flags |= UPF_DEAD;   //设置端口不可用标志
 mutex_unlock(&state->mutex);

 /*
  * Remove the devices from the tty layer
  */
 tty_unregister_device(drv->tty_driver, port->line); //端口对应设备从内核注销掉

 info = state->info;
 if (info && info->port.tty)
  tty_vhangup(info->port.tty);//处理tty挂起相关操作do_tty_hungup函数

 /*
  * All users of this port should now be disconnected from
  * this driver, and the port shut down.  We should be the
  * only thread fiddling with this port from now on.
  */
 state->info = NULL;

 /*
  * Free the port IO and memory resources, if any.
  */
 if (port->type != PORT_UNKNOWN)
  port->ops->release_port(port); //释放端口资源

 /*
  * Indicate that there isn't a port here anymore.
  */
 port->type = PORT_UNKNOWN;

 /*
  * Kill the tasklet, and free resources.
  */
 if (info) {
  tasklet_kill(&info->tlet);//下半部机制确保tasklet不再被调用
  kfree(info);
 }

 state->port = NULL;
 mutex_unlock(&port_mutex);

 return 0;
}

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐