目录
并不是所有的串口操作都有实现。具体如下:
1. open
当一个串行端口被打开时调用。在标准打开的函数usb_serial_generic_open前复位芯片
usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
FTDI_SIO_RESET_REQUEST, FTDI_SIO_RESET_REQUEST_TYPE,
FTDI_SIO_RESET_SIO,
priv->channel, NULL, 0, WDR_TIMEOUT);
然后通过调用ftdi_set_termios,更新与tty关联的串行通信设置,例如波特率、数据位、停止位和奇偶校验,以适应FTDI设备的要求。
if (tty)
ftdi_set_termios(tty, port, NULL);
2. set_termios
设置新的终端属性设置,如波特率、数据位、奇偶校验等。
2.1 设置波特率
if (priv->force_baud && ((termios->c_cflag & CBAUD) != B0)) {
dev_dbg(ddev, "%s: forcing baud rate for this device\n", __func__);
tty_encode_baud_rate(tty, priv->force_baud,
priv->force_baud);
}
force_baud是表示设备会被强制设置的波特率,而不是根据参数termiso->c_cflag里面的值设置。tty_encode_baud_rate后面2个参数分别表示读写的波特率,这个函数只是修改tty->c_cflag,没有实际操作芯片。
mutex_lock(&priv->cfg_lock);
if (change_speed(tty, port))
dev_err(ddev, "%s urb failed to set baudrate\n", __func__);
mutex_unlock(&priv->cfg_lock);
change_speed是设置波特率的函数。
2.2 设置数据位数
数据位,停止位和校验方式是通过一个命令写入的。
if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
FTDI_SIO_SET_DATA_REQUEST,
FTDI_SIO_SET_DATA_REQUEST_TYPE,
value, priv->channel,
NULL, 0, WDR_SHORT_TIMEOUT) < 0) {
dev_err(ddev, "%s FAILED to set databits/stopbits/parity\n",
__func__);
}
数据位有5/7/8三种方式,8位为默认方式。
switch (cflag & CSIZE) {
case CS5:
dev_dbg(ddev, "Setting CS5 quirk\n");
break;
case CS7:
value |= 7;
dev_dbg(ddev, "Setting CS7\n");
break;
default:
case CS8:
value |= 8;
dev_dbg(ddev, "Setting CS8\n");
break;
}
2.3 设置停止位
停止位有1或2两种设定
value |= (cflag & CSTOPB ? FTDI_SIO_SET_DATA_STOP_BITS_2 :
FTDI_SIO_SET_DATA_STOP_BITS_1);
2.4 设置校验方式
支持5种校验方式。
if (cflag & PARENB) {
if (cflag & CMSPAR)
value |= cflag & PARODD ?
FTDI_SIO_SET_DATA_PARITY_MARK :
FTDI_SIO_SET_DATA_PARITY_SPACE;
else
value |= cflag & PARODD ?
FTDI_SIO_SET_DATA_PARITY_ODD :
FTDI_SIO_SET_DATA_PARITY_EVEN;
} else {
value |= FTDI_SIO_SET_DATA_PARITY_NONE;
}
2.5 设置流控
和波特率一样,流控也是有强制设置的
if (priv->force_rtscts) {
dev_dbg(ddev, "%s: forcing rtscts for this device\n", __func__);
termios->c_cflag |= CRTSCTS;
}
如果波特率设置为0,也需要关闭流控。
if ((cflag & CBAUD) == B0) {
/* Disable flow control */
if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
FTDI_SIO_SET_FLOW_CTRL_REQUEST,
FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
0, priv->channel,
NULL, 0, WDR_TIMEOUT) < 0) {
dev_err(ddev, "%s error from disable flowcontrol urb\n",
__func__);
}
/* Drop RTS and DTR */
clear_mctrl(port, TIOCM_DTR | TIOCM_RTS);
}
最后是根据流控方式设置RTS/CTS或XON/XOFF的流控
value = 0;
if (C_CRTSCTS(tty)) {
dev_dbg(&port->dev, "enabling rts/cts flow control\n");
index = FTDI_SIO_RTS_CTS_HS;
} else if (I_IXON(tty)) {
dev_dbg(&port->dev, "enabling xon/xoff flow control\n");
index = FTDI_SIO_XON_XOFF_HS;
value = STOP_CHAR(tty) << 8 | START_CHAR(tty);
} else {
dev_dbg(&port->dev, "disabling flow control\n");
index = FTDI_SIO_DISABLE_FLOW_CTRL;
}
index |= priv->channel;
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
FTDI_SIO_SET_FLOW_CTRL_REQUEST,
FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
value, index, NULL, 0, WDR_TIMEOUT);
3. ioctl
处理来自应用程序的输入输出控制命令,这里只处理TIOCSERGETLSR命令,调用`get_lsr_info`函数来获取线路状态寄存器(LSR)的信息。
case TIOCSERGETLSR:
return get_lsr_info(port, argp);
4. get_serial
提供串行端口的状态信息给用户空间程序。
ss->flags = priv->flags;
ss->baud_base = priv->baud_base;
ss->custom_divisor = priv->custom_divisor;
- 将`priv->flags`复制到`ss->flags`:串行端口的标志或状态信息,例如数据位数等信息。
- 将`priv->baud_base`复制到`ss->baud_base`:串行端口的基本波特率,用于计算实际的波特率设置。
- 将`priv->custom_divisor`复制到`ss->custom_divisor`:这是一个自定义的除数,用于调整波特率。
5. set_serial
允许用户空间程序更改串行端口的状态信息。
首先要判断权限,通过capable(CAP_SYS_ADMIN)
函数检查当前进程是否具有管理员权限。如果当前进程没有管理员权限,则进一步检查ss->flags
和priv->flags
的差异是否涉及ASYNC_USR_MASK
之外的位。如果存在差异且涉及其他位,则释放priv->cfg_lock
互斥锁并返回-EPERM
错误码,表示权限不足。
if (!capable(CAP_SYS_ADMIN)) {
if ((ss->flags ^ priv->flags) & ~ASYNC_USR_MASK) {
mutex_unlock(&priv->cfg_lock);
return -EPERM;
}
}
更新latency时间和波特率
old_flags = priv->flags;
old_divisor = priv->custom_divisor;
priv->flags = ss->flags & ASYNC_FLAGS;
priv->custom_divisor = ss->custom_divisor;
write_latency_timer(port);
if ((priv->flags ^ old_flags) & ASYNC_SPD_MASK ||
((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST &&
priv->custom_divisor != old_divisor)) {
/* warn about deprecation unless clearing */
if (priv->flags & ASYNC_SPD_MASK)
dev_warn_ratelimited(&port->dev, "use of SPD flags is deprecated\n");
change_speed(tty, port);
}
6. break_ctl
控制断电信号(Break Condition),即在串行线路上发送一个持续的低电平信号。
if (break_state)
value = priv->last_set_data_value | FTDI_SIO_SET_BREAK;
else
value = priv->last_set_data_value;
ret = usb_control_msg(port->serial->dev,
usb_sndctrlpipe(port->serial->dev, 0),
FTDI_SIO_SET_DATA_REQUEST,
FTDI_SIO_SET_DATA_REQUEST_TYPE,
value, priv->channel,
NULL, 0, WDR_TIMEOUT);
7. tx_empty
检查发送缓冲区是否为空。
从FTDI芯片读取2个字节的modem status,其中第二个字节的位6表示该状态。
* Byte 1: Line Status
*
* Offset Description
* B0 Data Ready (DR)
* B1 Overrun Error (OE)
* B2 Parity Error (PE)
* B3 Framing Error (FE)
* B4 Break Interrupt (BI)
* B5 Transmitter Holding Register (THRE)
* B6 Transmitter Empty (TEMT)
* B7 Error in RCVR FIFO
ret = ftdi_get_modem_status(port, buf);
if (ret == 2) {
if (!(buf[1] & FTDI_RS_TEMT))
return false;
}
8. tiocmget
获取串行端口的调制解调器状态。
也是通过ftdi_get_modem_status获取FTDI芯片的2个字节modom status。主要是返回第一个字节和dtr/rts的状态。
* Byte 0: Modem Status
*
* Offset Description
* B0 Reserved - must be 1
* B1 Reserved - must be 0
* B2 Reserved - must be 0
* B3 Reserved - must be 0
* B4 Clear to Send (CTS)
* B5 Data Set Ready (DSR)
* B6 Ring Indicator (RI)
* B7 Receive Line Signal Detect (RLSD)
#define TIOCM_DSR 0x100
#define TIOCM_CTS 0x020
#define TIOCM_RNG 0x080
#define TIOCM_RI TIOCM_RNG
#define TIOCM_CAR 0x040
#define TIOCM_CD TIOCM_CAR
ret = (buf[0] & FTDI_SIO_DSR_MASK ? TIOCM_DSR : 0) |
(buf[0] & FTDI_SIO_CTS_MASK ? TIOCM_CTS : 0) |
(buf[0] & FTDI_SIO_RI_MASK ? TIOCM_RI : 0) |
(buf[0] & FTDI_SIO_RLSD_MASK ? TIOCM_CD : 0) |
priv->last_dtr_rts;
9. tiocmset
设置串行端口的调制解调器状态。