usbserial驱动流程解析_Part2_初始化流程_以probe为例(echo cat测试回环打印不停问题解决)

本文详细解析了USB转串口设备的初始化过程,涉及tty设备操作函数、USB设备probe流程中的endpoint设置、以及init_termios参数配置,包括如何处理默认ECHO模式导致的问题和USB设备注册为TTYUSB设备的步骤。
摘要由CSDN通过智能技术生成


usb转串口设备需要注册usb侧和serial侧两侧的操作,本文将简要分析二者的初始化流程以及一些关键函数的初始化流程。

module_init(usb_serial_init);

tty设备初始化

e9da8ed6708c4cde93fe39dded64386d.png

内核会直接调用usb_serial_init,开始进行usb和serial的初始化,首先是进行tty设备(text typer,原有的字符打印机被称为tty,现在字符设备都称为tty设备,比如serial)初始化:

static const struct tty_operations serial_ops = {
	.open =			serial_open,
	.close =		serial_close,
	.write =		serial_write,
	.hangup =		serial_hangup,
	.write_room =		serial_write_room,
	.ioctl =		serial_ioctl,
	.set_termios =		serial_set_termios,
	.throttle =		serial_throttle,
	.unthrottle =		serial_unthrottle,
	.break_ctl =		serial_break,
	.chars_in_buffer =	serial_chars_in_buffer,
	.wait_until_sent =	serial_wait_until_sent,
	.tiocmget =		serial_tiocmget,
	.tiocmset =		serial_tiocmset,
	.get_icount =		serial_get_icount,
	.set_serial =		serial_set_serial,
	.get_serial =		serial_get_serial,
	.cleanup =		serial_cleanup,
	.install =		serial_install,
	.proc_show =		serial_proc_show,
};

这一部分是tty operation 的指定,包括串口设备的open close write writeroom settermios install等比较关键的函数,上一part进行了相应的描述,其中部分函数可以被替换掉。

	usb_serial_tty_driver->driver_name = "usbserial";
	usb_serial_tty_driver->name = "ttyUSB";
	usb_serial_tty_driver->major = USB_SERIAL_TTY_MAJOR;
	usb_serial_tty_driver->minor_start = 0;
	usb_serial_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
	usb_serial_tty_driver->subtype = SERIAL_TYPE_NORMAL;
	usb_serial_tty_driver->init_termios = tty_std_termios;
	usb_serial_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD
							| HUPCL | CLOCAL;
	usb_serial_tty_driver->init_termios.c_ispeed = 9600;
	usb_serial_tty_driver->init_termios.c_ospeed = 9600;

指定了init termios的值,比如波特率初始9600,初始的termios取tty_std_termios,这里有一个坑,将usb转串口模块的tx rx短接,试图测回环的时候,使用默认驱动会出现cat  /dev/ttyUSB0并echo xxx > /dev/ttyUSB0时,cat到的字符串越来越长,根本停不下来——

原因是默认情况下,rx收到包会再次从tx发出去,解决方法有2:

不短接tx rx:外接另一个usb转串口,tx接rx、rx接tx、共GND,这样在echo的时候,在对端可以看到echo过去的字符串,对端进行tx的时候,可以从cat获得字符串,同时,对端的rx也会收到发出去的字符串

初始化:使用putty或者minicom等软件,初始化一下usb转串口设备,再短接rx tx,再echo和cat就不会再有问题了

根本的原因是这样的:默认情况下termios取tty_std_termios导致的问题时,tty_std_termios定义在tty_io.c里面,里面init_termios.l_cflag取值有ECHO属性,换言之,read urb后调用write,是驱动以外的操作,是内核完成的,而这个属性,是ECHO属性,被内核读取到后,内核去进行以上操作。

usb设备初始化

 下图是usb部分的probe的指定调用流程图,图的顺序是从下往上看的:

525aa9131c964400a326a40d267f8b81.png

在函数usb_serial_register_drivers中,注册了usb driver的关键内容:

    udriver->name = name;
	udriver->no_dynamic_id = 1;
	udriver->supports_autosuspend = 1;
	udriver->suspend = usb_serial_suspend;
	udriver->resume = usb_serial_resume;
	udriver->probe = usb_serial_probe;
	udriver->disconnect = usb_serial_disconnect;

	/* we only set the reset_resume field if the serial_driver has one */
	for (sd = serial_drivers; *sd; ++sd) {
		if ((*sd)->reset_resume) {
			udriver->reset_resume = usb_serial_reset_resume;
			break;
		}
	}

	rc = usb_register(udriver);

usb_register函数会把usb侧probe、disconnect等函数的实现传给内核

以USB的probe过程分析整体调用的流程

在USB转串口模块插入USB接口时,Linux内核会调用usb_serial_probe函数(上一个part只分析到调用到usb_serial_generic_probe函数,在调用usb_serial_generic_probe之后后面的操作并没有描述):

epds = kzalloc(sizeof(*epds), GFP_KERNEL);
	if (!epds) {
		retval = -ENOMEM;
		goto err_release_sibling;
	}

	find_endpoints(serial, epds, interface);
	if (serial->sibling)
		find_endpoints(serial, epds, serial->sibling);

	if (epds->num_bulk_in < type->num_bulk_in ||
			epds->num_bulk_out < type->num_bulk_out ||
			epds->num_interrupt_in < type->num_interrupt_in ||
			epds->num_interrupt_out < type->num_interrupt_out) {
		dev_err(ddev, "required endpoints missing\n");
		retval = -ENODEV;
		goto err_free_epds;
	}

	if (type->calc_num_ports) {
		retval = type->calc_num_ports(serial, epds);
		if (retval < 0)
			goto err_free_epds;
		num_ports = retval;
	}

	if (!num_ports)
		num_ports = type->num_ports;

	if (num_ports > MAX_NUM_PORTS) {
		dev_warn(ddev, "too many ports requested: %d\n", num_ports);
		num_ports = MAX_NUM_PORTS;
	}

usb的probe进行过程中,驱动会去find_endpoints(serial, epds, interface);,扫描endpoint后把扫到的addr存在serial的变量里面:

	serial->num_ports = (unsigned char)num_ports;
	serial->num_bulk_in = epds->num_bulk_in;
	serial->num_bulk_out = epds->num_bulk_out;
	serial->num_interrupt_in = epds->num_interrupt_in;
	serial->num_interrupt_out = epds->num_interrupt_out;

	/* found all that we need */
	dev_info(ddev, "%s converter detected\n", type->description);

	/* create our ports, we need as many as the max endpoints */
	/* we don't use num_ports here because some devices have more
	   endpoint pairs than ports */
	max_endpoints = max(epds->num_bulk_in, epds->num_bulk_out);
	max_endpoints = max(max_endpoints, epds->num_interrupt_in);
	max_endpoints = max(max_endpoints, epds->num_interrupt_out);
	max_endpoints = max(max_endpoints, serial->num_ports);
	serial->num_port_pointers = max_endpoints;

	dev_dbg(ddev, "setting up %d port structure(s)\n", max_endpoints);
	for (i = 0; i < max_endpoints; ++i) {
		port = kzalloc(sizeof(struct usb_serial_port), GFP_KERNEL);
		if (!port) {
			retval = -ENOMEM;
			goto err_free_epds;
		}
		tty_port_init(&port->port);
		port->port.ops = &serial_port_ops;
		port->serial = serial;
		spin_lock_init(&port->lock);
		/* Keep this for private driver use for the moment but
		   should probably go away */
		INIT_WORK(&port->work, usb_serial_port_work);
		serial->port[i] = port;
		port->dev.parent = &interface->dev;
		port->dev.driver = NULL;
		port->dev.bus = &usb_serial_bus_type;
		port->dev.release = &usb_serial_port_release;
		port->dev.groups = usb_serial_port_groups;
		device_initialize(&port->dev);
	}

存好了bulkin bulkout等地址就可以为后面的read和write的操作提供endpoint地址,保存后对这些endpoint进行初始化,初始化的过程就是先给urb进行一系列的初始化,指定bulkin bulkout的addr并绑定到对应的port,在read和write的时候直接修改urb里面的数据或者获取里面的数据,就可以直接submit urb即可。

	/* set up the endpoint information */
	for (i = 0; i < epds->num_bulk_in; ++i) {
		retval = setup_port_bulk_in(serial->port[i], epds->bulk_in[i]);
		if (retval)
			goto err_free_epds;
	}

	for (i = 0; i < epds->num_bulk_out; ++i) {
		retval = setup_port_bulk_out(serial->port[i],
				epds->bulk_out[i]);
		if (retval)
			goto err_free_epds;
	}

	if (serial->type->read_int_callback) {
		for (i = 0; i < epds->num_interrupt_in; ++i) {
			retval = setup_port_interrupt_in(serial->port[i],
					epds->interrupt_in[i]);
			if (retval)
				goto err_free_epds;
		}
	} else if (epds->num_interrupt_in) {
		dev_dbg(ddev, "The device claims to support interrupt in transfers, but read_int_callback is not defined\n");
	}

	if (serial->type->write_int_callback) {
		for (i = 0; i < epds->num_interrupt_out; ++i) {
			retval = setup_port_interrupt_out(serial->port[i],
					epds->interrupt_out[i]);
			if (retval)
				goto err_free_epds;
		}
	} else if (epds->num_interrupt_out) {
		dev_dbg(ddev, "The device claims to support interrupt out transfers, but write_int_callback is not defined\n");
	}

	usb_set_intfdata(interface, serial);

	/* if this device type has an attach function, call it */
	if (type->attach) {
		retval = type->attach(serial);
		if (retval < 0)
			goto err_free_epds;
		serial->attached = 1;
		if (retval > 0) {
			/* quietly accept this device, but don't bind to a
			   serial port as it's about to disappear */
			serial->num_ports = 0;
			goto exit;
		}
	} else {
		serial->attached = 1;
	}

	retval = allocate_minors(serial, num_ports);
	if (retval) {
		dev_err(ddev, "No more free serial minor numbers\n");
		goto err_free_epds;
	}

在probe的最后 ,在linux系统注册设备为TTYUSB0.

	/* register all of the individual ports with the driver core */
	for (i = 0; i < num_ports; ++i) {
		port = serial->port[i];
		dev_set_name(&port->dev, "ttyUSB%d", port->minor);
		dev_dbg(ddev, "registering %s\n", dev_name(&port->dev));
		device_enable_async_suspend(&port->dev);

		retval = device_add(&port->dev);
		if (retval)
			dev_err(ddev, "Error registering port device, continuing\n");
	}

	if (num_ports > 0)
		usb_serial_console_init(serial->port[0]->minor);

 

 

  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值