本节主要分析虚拟串口的tty设备的注册、创建/dev/ttyGSx设备节点、tty相关接口的实现。

tty的申请与注册

源码:drivers/usb/gadget/function/u_serial.c

static const struct tty_operations gs_tty_ops = {
.open = gs_open,
.close = gs_close,
.write = gs_write,
.put_char = gs_put_char,
.flush_chars = gs_flush_chars,
.write_room = gs_write_room,
.chars_in_buffer = gs_chars_in_buffer,
.unthrottle = gs_unthrottle,
.break_ctl = gs_break_ctl,
}; static int userial_init(void)
{
unsigned i;
int status; //申请tty_driver驱动
gs_tty_driver = alloc_tty_driver(MAX_U_SERIAL_PORTS);
if (!gs_tty_driver)
return -ENOMEM; //初始化tty_driver,设备节点名
gs_tty_driver->driver_name = "g_serial";
gs_tty_driver->name = "ttyGS";
/* uses dynamically assigned dev_t values */ gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
gs_tty_driver->subtype = SERIAL_TYPE_NORMAL;
gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
gs_tty_driver->init_termios = tty_std_termios; /* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on
* MS-Windows. Otherwise, most of these flags shouldn't affect
* anything unless we were to actually hook up to a serial line.
*/
gs_tty_driver->init_termios.c_cflag =
B9600 | CS8 | CREAD | HUPCL | CLOCAL;
gs_tty_driver->init_termios.c_ispeed = 9600;
gs_tty_driver->init_termios.c_ospeed = 9600; //tty_driver的tty_operations初始化
tty_set_operations(gs_tty_driver, &gs_tty_ops);
for (i = 0; i < MAX_U_SERIAL_PORTS; i++)
mutex_init(&ports[i].lock); /* export the driver ... */
status = tty_register_driver(gs_tty_driver);
if (status) {
pr_err("%s: cannot register, err %d\n",
__func__, status);
goto fail;
} pr_debug("%s: registered %d ttyGS* device%s\n", __func__,
MAX_U_SERIAL_PORTS,
(MAX_U_SERIAL_PORTS == 1) ? "" : "s"); return status;
fail:
put_tty_driver(gs_tty_driver);
gs_tty_driver = NULL;
return status;
}
module_init(userial_init); static void userial_cleanup(void)
{
tty_unregister_driver(gs_tty_driver);
put_tty_driver(gs_tty_driver);
gs_tty_driver = NULL;
}
module_exit(userial_cleanup);

connect与disconnect

conect将会使能数据传输用的endpoint,同时启动数据数据的功能。

/**
* gserial_connect - notify TTY I/O glue that USB link is active
* @gser: the function, set up with endpoints and descriptors
* @port_num: which port is active
* Context: any (usually from irq)
*
* This is called activate endpoints and let the TTY layer know that
* the connection is active ... not unlike "carrier detect". It won't
* necessarily start I/O queues; unless the TTY is held open by any
* task, there would be no point. However, the endpoints will be
* activated so the USB host can perform I/O, subject to basic USB
* hardware flow control.
*
* Caller needs to have set up the endpoints and USB function in @dev
* before calling this, as well as the appropriate (speed-specific)
* endpoint descriptors, and also have allocate @port_num by calling
* @gserial_alloc_line().
*
* Returns negative errno or zero.
* On success, ep->driver_data will be overwritten.
*/
int gserial_connect(struct gserial *gser, u8 port_num)
{
struct gs_port *port;
unsigned long flags;
int status; if (port_num >= MAX_U_SERIAL_PORTS)
return -ENXIO; port = ports[port_num].port;
if (!port) {
pr_err("serial line %d not allocated.\n", port_num);
return -EINVAL;
}
if (port->port_usb) {
pr_err("serial line %d is in use.\n", port_num);
return -EBUSY;
} /* activate the endpoints */
//使能in_endpoint,可用于in数据传输
status = usb_ep_enable(gser->in);
if (status < 0)
return status;
gser->in->driver_data = port; //使能out_endpoint,可用于out数据传输
status = usb_ep_enable(gser->out);
if (status < 0)
goto fail_out;
gser->out->driver_data = port; /* then tell the tty glue that I/O can work */
spin_lock_irqsave(&port->port_lock, flags);
gser->ioport = port;
port->port_usb = gser; /* REVISIT unclear how best to handle this state...
* we don't really couple it with the Linux TTY.
*/
gser->port_line_coding = port->port_line_coding; /* REVISIT if waiting on "carrier detect", signal. */ /* if it's already open, start I/O ... and notify the serial
* protocol about open/close status (connect/disconnect).
*/
//若当前TTYGSx设备节点已打开,则启动tx/rx的功能
//并调用gserial的connect功能,详见drivers/usb/gadget/function/f_acm.c的acm_connect与acm_disconnect
if (port->port.count) {
pr_debug("gserial_connect: start ttyGS%d\n", port->port_num);
gs_start_io(port); if (gser->connect)
gser->connect(gser);
} else {
if (gser->disconnect)
gser->disconnect(gser);
} status = gs_console_connect(port_num);
spin_unlock_irqrestore(&port->port_lock, flags); return status; fail_out:
usb_ep_disable(gser->in);
return status;
}
EXPORT_SYMBOL_GPL(gserial_connect);
/**
* gserial_disconnect - notify TTY I/O glue that USB link is inactive
* @gser: the function, on which gserial_connect() was called
* Context: any (usually from irq)
*
* This is called to deactivate endpoints and let the TTY layer know
* that the connection went inactive ... not unlike "hangup".
*
* On return, the state is as if gserial_connect() had never been called;
* there is no active USB I/O on these endpoints.
*/
void gserial_disconnect(struct gserial *gser)
{
struct gs_port *port = gser->ioport;
unsigned long flags; if (!port)
return; /* tell the TTY glue not to do I/O here any more */
spin_lock_irqsave(&port->port_lock, flags); /* REVISIT as above: how best to track this? */
port->port_line_coding = gser->port_line_coding; port->port_usb = NULL;
gser->ioport = NULL;
if (port->port.count > 0 || port->openclose) {
wake_up_interruptible(&port->drain_wait);
if (port->port.tty)
tty_hangup(port->port.tty);
}
spin_unlock_irqrestore(&port->port_lock, flags); /* disable endpoints, aborting down any active I/O */
usb_ep_disable(gser->out);
usb_ep_disable(gser->in); /* finally, free any unused/unusable I/O buffers */
spin_lock_irqsave(&port->port_lock, flags);
if (port->port.count == 0 && !port->openclose)
kfifo_free(&port->port_write_buf);
gs_free_requests(gser->out, &port->read_pool, NULL);
gs_free_requests(gser->out, &port->read_queue, NULL);
gs_free_requests(gser->in, &port->write_pool, NULL); port->read_allocated = port->read_started =
port->write_allocated = port->write_started = 0; gs_console_disconnect(gser->in);
spin_unlock_irqrestore(&port->port_lock, flags);
}
EXPORT_SYMBOL_GPL(gserial_disconnect);

gs_start_io

/**
* gs_start_io - start USB I/O streams
* @dev: encapsulates endpoints to use
* Context: holding port_lock; port_tty and port_usb are non-null
*
* We only start I/O when something is connected to both sides of
* this port. If nothing is listening on the host side, we may
* be pointlessly filling up our TX buffers and FIFO.
*/
static int gs_start_io(struct gs_port *port)
{
struct list_head *head = &port->read_pool;
struct usb_ep *ep = port->port_usb->out;
int status;
unsigned started; /* Allocate RX and TX I/O buffers. We can't easily do this much
* earlier (with GFP_KERNEL) because the requests are coupled to
* endpoints, as are the packet sizes we'll be using. Different
* configurations may use different endpoints with a given port;
* and high speed vs full speed changes packet sizes too.
*/
//申请out endpoint的req,最终会回调到udc驱动中usb_ep_ops的alloc_request
//详见:drivers/usb/gadget/udc/s3c2410_udc.c
status = gs_alloc_requests(ep, head, gs_read_complete,
&port->read_allocated);
if (status)
return status; //申请in endpoint的req,最终会回调到udc驱动中usb_ep_ops的alloc_request
//详见:drivers/usb/gadget/udc/s3c2410_udc.c
status = gs_alloc_requests(port->port_usb->in, &port->write_pool,
gs_write_complete, &port->write_allocated);
if (status) {
gs_free_requests(ep, head, &port->read_allocated);
return status;
} /* queue read requests */
port->n_read = 0;
started = gs_start_rx(port); if (started) {
gs_start_tx(port);
/* Unblock any pending writes into our circular buffer, in case
* we didn't in gs_start_tx() */
tty_wakeup(port->port.tty);
} else {
gs_free_requests(ep, head, &port->read_allocated);
gs_free_requests(port->port_usb->in, &port->write_pool,
&port->write_allocated);
status = -EIO;
} return status;
}
static void gs_free_requests(struct usb_ep *ep, struct list_head *head,
int *allocated)
{
struct usb_request *req; while (!list_empty(head)) {
req = list_entry(head->next, struct usb_request, list);
list_del(&req->list);
gs_free_req(ep, req);
if (allocated)
(*allocated)--;
}
} static int gs_alloc_requests(struct usb_ep *ep, struct list_head *head,
void (*fn)(struct usb_ep *, struct usb_request *),
int *allocated)
{
int i;
struct usb_request *req;
//由于*allocated初始化为0,则默认申请req的数量为QUEUE_SIZE=16
int n = allocated ? QUEUE_SIZE - *allocated : QUEUE_SIZE; /* Pre-allocate up to QUEUE_SIZE transfers, but if we can't
* do quite that many this time, don't fail ... we just won't
* be as speedy as we might otherwise be.
*/
for (i = 0; i < n; i++) {
req = gs_alloc_req(ep, ep->maxpacket, GFP_ATOMIC);
if (!req)
return list_empty(head) ? -ENOMEM : 0;
req->complete = fn;
list_add_tail(&req->list, head);
if (allocated)
(*allocated)++;
}
return 0;
}

gs_start_rx与gs_start_tx

/*
* Context: caller owns port_lock, and port_usb is set
*/
static unsigned gs_start_rx(struct gs_port *port)
/*
__releases(&port->port_lock)
__acquires(&port->port_lock)
*/
{
struct list_head *pool = &port->read_pool;
struct usb_ep *out = port->port_usb->out; //read_pool不为空
while (!list_empty(pool)) {
struct usb_request *req;
int status;
struct tty_struct *tty; /* no more rx if closed */
tty = port->port.tty;
if (!tty)
break; if (port->read_started >= QUEUE_SIZE)
break; //从list链表中取出空闲的req
req = list_entry(pool->next, struct usb_request, list);
list_del(&req->list);
req->length = out->maxpacket; /* drop lock while we call out; the controller driver
* may need to call us back (e.g. for disconnect)
*/
spin_unlock(&port->port_lock);
//将取出的req插入到out endpoint 的queue中
status = usb_ep_queue(out, req, GFP_ATOMIC);
spin_lock(&port->port_lock); if (status) {
pr_debug("%s: %s %s err %d\n",
__func__, "queue", out->name, status);
list_add(&req->list, pool);
break;
}
port->read_started++; /* abort immediately after disconnect */
if (!port->port_usb)
break;
}
return port->read_started;
}
/*
* gs_start_tx
*
* This function finds available write requests, calls
* gs_send_packet to fill these packets with data, and
* continues until either there are no more write requests
* available or no more data to send. This function is
* run whenever data arrives or write requests are available.
*
* Context: caller owns port_lock; port_usb is non-null.
*/
static int gs_start_tx(struct gs_port *port)
/*
__releases(&port->port_lock)
__acquires(&port->port_lock)
*/
{
struct list_head *pool = &port->write_pool;
struct usb_ep *in;
int status = 0;
bool do_tty_wake = false; if (!port->port_usb)
return status; in = port->port_usb->in; while (!port->write_busy && !list_empty(pool)) {
struct usb_request *req;
int len; if (port->write_started >= QUEUE_SIZE)
break; //从list中取出空闲的req
req = list_entry(pool->next, struct usb_request, list);
//将需发送的数据填充到req->buf中
len = gs_send_packet(port, req->buf, in->maxpacket);
if (len == 0) {
wake_up_interruptible(&port->drain_wait);
break;
}
do_tty_wake = true; req->length = len;
list_del(&req->list);
req->zero = kfifo_is_empty(&port->port_write_buf); pr_vdebug("ttyGS%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n",
port->port_num, len, *((u8 *)req->buf),
*((u8 *)req->buf+1), *((u8 *)req->buf+2)); /* Drop lock while we call out of driver; completions
* could be issued while we do so. Disconnection may
* happen too; maybe immediately before we queue this!
*
* NOTE that we may keep sending data for a while after
* the TTY closed (dev->ioport->port_tty is NULL).
*/
port->write_busy = true;
spin_unlock(&port->port_lock);
//将取出的req插入到in endpoint的queue尾部
status = usb_ep_queue(in, req, GFP_ATOMIC);
spin_lock(&port->port_lock);
port->write_busy = false; if (status) {
pr_debug("%s: %s %s err %d\n",
__func__, "queue", in->name, status);
list_add(&req->list, pool);
break;
} port->write_started++; /* abort immediately after disconnect */
if (!port->port_usb)
break;
} if (do_tty_wake && port->port.tty)
tty_wakeup(port->port.tty);
return status;
}

rx/tx的complete

在udc驱动中endpoint对应的req完成后,将会回调其complete函数,完成后续的动作。

static void gs_read_complete(struct usb_ep *ep, struct usb_request *req)
{
struct gs_port *port = ep->driver_data; /* Queue all received data until the tty layer is ready for it. */
spin_lock(&port->port_lock);
//将当前接收到数据req,插入到read_queue的尾部
list_add_tail(&req->list, &port->read_queue); //唤醒gs_rx_push处理数据
tasklet_schedule(&port->push);
spin_unlock(&port->port_lock);
}
static void gs_write_complete(struct usb_ep *ep, struct usb_request *req)
{
struct gs_port *port = ep->driver_data; spin_lock(&port->port_lock);
list_add(&req->list, &port->write_pool);
port->write_started--; switch (req->status) {
default:
/* presumably a transient fault */
pr_warn("%s: unexpected %s status %d\n",
__func__, ep->name, req->status);
/* FALL THROUGH */
case 0:
/* normal completion */
gs_start_tx(port);
break; case -ESHUTDOWN:
/* disconnect */
pr_vdebug("%s: %s shutdown\n", __func__, ep->name);
break;
} spin_unlock(&port->port_lock);
}

USB gadget驱动框架(五)的更多相关文章

  1. USB摄像头驱动框架分析(五)

    一.USB摄像头驱动框架如下所示:1.构造一个usb_driver2.设置   probe:        2.1. 分配video_device:video_device_alloc        ...

  2. USB gadget 驱动 printer.c 分析

    1. modprobe g_printer idVendor=0x0525 idProduct=0xa4a8 modprobe后面也可以加模块参数 2. prn_example从stdout获取数据然 ...

  3. USB摄像头驱动框架分析

    usb摄像头驱动程序,里面涉及硬件的操作.比如说,想设置亮度的时候,需要把亮度的参数发给硬件.去得到真正视频数据的时候,需要访问硬件得到数据.usb摄像头驱动程序框架与虚拟摄像头驱动程序的框架是一样的 ...

  4. I2C驱动框架(五)

    参考:I2C子系统之 adapter driver注册——I2C_dev_init() i2c的操作在内核中是当做字符设备来操作的,相关初始化在由i2c_dev_init函数来初始化. static ...

  5. Linux USB ECM Gadget 驱动介绍

    ​1 USB ECM介绍 USB ECM,属于USB-IF定义的CDC(Communication Device Class)下的一个子类:Ethernet Networking Control Mo ...

  6. Linux下USB驱动框架分析【转】

    转自:http://blog.csdn.net/brucexu1978/article/details/17583407 版权声明:本文为博主原创文章,未经博主允许不得转载. http://www.c ...

  7. Linux usb子系统(一) _写一个usb鼠标驱动

    USB总线是一种典型的热插拔的总线标准,由于其优异的性能几乎成为了当下大小设备中的标配. USB的驱动可以分为3类:SoC的USB控制器的驱动,主机端USB设备的驱动,设备上的USB Gadget驱动 ...

  8. linux usb总线驱动(一)

    目录 linux usb总线驱动框架 USB 介绍 传输类型 控制器接口 2440接口 基本流程 alloc_dev choose_address hub_port_init usb_get_devi ...

  9. Linux 串口、usb转串口驱动分析(2-2) 【转】

    转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26807463&id=4186852 Linux 串口.usb转 ...

  10. Linux 串口、usb转串口驱动分析(2-1) 【转】

    转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26807463&id=4186851 Linux 串口.usb转 ...

随机推荐

  1. pycham配置GitHub环境【一文了解window上GitHub的基本操作】

    基础用户设置[包含用户登录.密钥生成] 网络配置 外观->系统设置->https代理->检查连接 我这里测试网址是GitHub,连接成功即可后续操作[不成功别找我,我也不知道] gi ...

  2. 【Windows】远程访问设置

    Windows自带了远程访问功能: Win + R 打开运行,输入[mstsc] 连接需要提供主机地址,和用户账号 下面的选项可以保存此连接为文件,下一次连接直接打开文件即可访问 当然设置了以后可能还 ...

  3. 国产AI训练卡,对标美国NVIDIA公司的A100,华为昇腾Atlas 300T A2(Ascend 910B4)高性能GPU/NPU/AI推理/国产计算/信创训练卡 —— 电商平台已开售

    China has successfully achieved the localization of AI chips, breaking through the technological res ...

  4. How to evaluate the Messi Hong Kong fraud incident?

    Who is Lionel Messi? URL: https://en.wikipedia.org/wiki/Lionel_Messi As a famous football player, Me ...

  5. 8月5日CSP-S模拟赛赛后总结

    8月5日CSP-S模拟赛赛后总结 \[8月5日 \ \ CSP-S模拟赛 \ \ 赛后总结 \\ 2024年8月5日 \\ by \ \ \ uhw177po \] 一.做题情况 第一题比赛 \(10 ...

  6. Java核心编程-第一卷:基础知识

    public static void main(String[] args) { BigInteger bigInteger1 = BigInteger.probablePrime(20, new R ...

  7. mongo变更流使用及windows下副本集五分钟搭建

    mongodb的变更流解释: 变更流(Change Streams)允许应用程序访问实时数据变更,从而避免事先手动追踪  oplog 的复杂性和风险.应用程序可使用变更流来订阅针对单个集合.数据库或整 ...

  8. 将 Rust 代码编译为 WASM

    前言 在现代 Web 开发中,WebAssembly (WASM) 已成为一种强大的工具.它使得开发者可以在浏览器中运行高性能的代码,跨越传统的 JavaScript 性能限制.Rust 语言因其高效 ...

  9. LLM应用实战: 产业治理多标签分类

    1. 背景 许久未见,甚是想念~ 近期本qiang~换了工作,处于新业务适应期,因此文章有一段时间未更新,理解万岁! 现在正在着手的工作是产业治理方面,主要负责其中一个功能模块,即按照产业治理标准体系 ...

  10. LaTeX 常用引用标签前缀

    引用对象 标签前缀 Chapter ch Section sec Subsection sec Appendix app Figure fig Table tab List item itm Equa ...