Linux-485收发切换延迟的解决方法 (2)

但serial_8250.c中默认并未实现rs485_config函数,那我们自己实现,如下:
1) 驱动层编写485配置函数

// add by xx@xx begin static int serial8250_rs485_config(struct uart_8250_port *up, struct serial_rs485 *rs485) { if (rs485->flags & SER_RS485_ENABLED) { printk(KERN_INFO "uart %d set 485 on\n", up->port.line); gpio_485_set_direction(true); gpio_485_set_value(false); tasklet_init(&s485_tasklet, serial8250_485_do_tasklet, (unsigned long)&up->port); } else { printk(KERN_INFO "uart %d set 485 off\n", up->port.line); } memcpy(&up->rs485, rs485, sizeof(*rs485)); return 0; } // add by xx@xx end

此函数在应用层调用ioctl()函数时,会被驱动层调用执行,此函数作了两件事:

a. 将第二个参数rs485保存在第一个参数up里,第一个参数关联具体的某个串口设备(关联应用层里的ioctl(fd)中的fd)

b. 判断参数是否使能了485模式,若使能了,则将485方向设置为接收,并注册中断底半部tasklet处理函数serial8250_485_do_tasklet

2) 驱动层注册485配置函数

int serial8250_register_8250_port(struct uart_8250_port *up) { struct uart_8250_port *uart; int ret = -ENOSPC; if (up->port.uartclk == 0) return -EINVAL; mutex_lock(&serial_mutex); // add by xx@xx begin memset((void *)&up->rs485, 0, sizeof(up->rs485)); up->rs485_config = serial8250_rs485_config; // add by xx@xx end ...... }

3)应用层open()打开串口时,驱动层调用链

serial8250_probe()-> serial8250_register_8250_port()-> up->rs485_config = serial8250_rs485_config;

4) 应用层ioctl()使能串口485模式时,ioctl()在驱动底层的调用代码

// 下列代码为系统自带代码,无任何改动 static int serial8250_ioctl(struct uart_port *port, unsigned int cmd, unsigned long arg) { struct uart_8250_port *up = container_of(port, struct uart_8250_port, port); int ret; struct serial_rs485 rs485_config; if (!up->rs485_config) return -ENOIOCTLCMD; switch (cmd) { case TIOCSRS485: // 设置 if (copy_from_user(&rs485_config, (void __user *)arg, sizeof(rs485_config))) return -EFAULT; ret = up->rs485_config(up, &rs485_config); if (ret) return ret; memcpy(&up->rs485, &rs485_config, sizeof(rs485_config)); return 0; case TIOCGRS485: // 获取 if (copy_to_user((void __user *)arg, &up->rs485, sizeof(up->rs485))) return -EFAULT; return 0; default: break; } return -ENOIOCTLCMD; }

调用链:

serial8250_ioctl()-> up->rs485_config(up, &rs485_config)-> serial8250_rs485_config() // 自己实现的函数

serial8250_rs485_config()说明参上

3.2 在发送过程的起始时刻拉高PIN_DIR

在串口发送的起始时刻,即串口产生传输起始位的时刻,会调用serial8250_start_tx(),在此函数中将PIN_DIR拉高

static void serial8250_start_tx(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); // add by xx@xx begin if (up->rs485.flags & SER_RS485_ENABLED) { gpio_485_set_value(true); } // add by xx@xx end ...... } 3.3 在发送过程的结束时间拉低PIN_DIR

按照推理,以为在串口传输结束位的时候,会调用serial8250_stop_tx(),那在此函数中将PIN_DIR拉低,任务就完成了。
但是,加打印发现,实际此函数从未被调用。
缕一下代码,找到串口发送的结束时刻:8250串口的收发数据是通过中断方式实现的,串口的结束时刻在中断处理程序中判断,
1) 中断处理函数的注册

serial8250_init()-> serial8250_isa_init_ports()-> set_io_from_upio()-> p->handle_irq = serial8250_default_handle_irq;

2) 中断处理函数的调用

serial8250_default_handle_irq()-> serial8250_handle_irq()-> serial8250_tx_chars()->

3) 找到位置了,就在serial8250_tx_chars()中调用底半部机制tasklet

void serial8250_tx_chars(struct uart_8250_port *up) { struct uart_port *port = &up->port; struct circ_buf *xmit = &port->state->xmit; int count; if (port->x_char) { serial_out(up, UART_TX, port->x_char); port->icount.tx++; port->x_char = 0; return; } if (uart_tx_stopped(port)) { serial8250_stop_tx(port); return; } if (uart_circ_empty(xmit)) { __stop_tx(up); return; } count = up->tx_loadsz; do { serial_out(up, UART_TX, xmit->buf[xmit->tail]); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); port->icount.tx++; if (uart_circ_empty(xmit)) break; if (up->capabilities & UART_CAP_HFIFO) { if ((serial_port_in(port, UART_LSR) & BOTH_EMPTY) != BOTH_EMPTY) break; } } while (--count > 0); if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(port); DEBUG_INTR("THRE..."); /* * With RPM enabled, we have to wait once the FIFO is empty before the * HW can go idle. So we get here once again with empty FIFO and disable * the interrupt and RPM in __stop_tx() */ if (uart_circ_empty(xmit) && !(up->capabilities & UART_CAP_RPM)) { __stop_tx(up); // add by xx@xx begin if (up->rs485.flags & SER_RS485_ENABLED) tasklet_hi_schedule(&s485_tasklet); // add by xx@xx end } }

注:tasklet_hi_schedule()和tasklet_schedule()的区别:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyyfyx.html