首页 > 其他分享 >USB设备驱动开发-USB Gadget Driver

USB设备驱动开发-USB Gadget Driver

时间:2023-01-30 11:22:53浏览次数:70  
标签:gadget struct Gadget Driver udc cdev usb ep USB

一、Linux USB Gadget Driver功能

为了与主机端驱动设备的USB Device Driver概念进行区别,将在外围器件中运行的驱动程序称为USB Gadget Driver。其中,Host端驱动设备的驱动程序是master或者client driver,设备端gadget driver是slave或者function driver。

       Gadget Driver和USB Host端驱动程序类似,都是使用请求队列来对I/O包进行缓冲,这些请求可以被提交和取消。它们的结构、消息和常量的定义也和USB技术规范第九章的内容一致。同时也是通过bind和unbind将driver与device建立关系。

二、Linux USB Gadget Driver核心数据结构

1. USB_Gadget对象

struct usb_gadget {

/* readonly to gadget driver */

const struct usb_gadget_ops *ops; //Gadget设备操作函数集

struct usb_ep *ep0; //控制端点,只对setup包响应

struct list_head ep_list;//将设备的所有端点连成链表,ep0不在其中

enum usb_device_speed speed;//高速、全速和低速

unsigned is_dualspeed:1; //是否同时支持高速和全速

unsigned is_otg:1; //是否支持OTG(On-To-Go)

unsigned is_a_peripheral:1;

unsigned b_hnp_enable:1;

unsigned a_hnp_support:1;

unsigned a_alt_hnp_support:1;

const char *name; //器件名称

struct device dev; //内核设备模型使用

};

2. Gadget器件操作函数集

操作UDC硬件的API,但操作端点的函数由端点操作函数集完成

struct usb_gadget_ops {

int (*get_frame)(struct usb_gadget *);

int (*wakeup)(struct usb_gadget *);

int (*set_selfpowered) (struct usb_gadget *, int is_selfpowered);

int (*vbus_session) (struct usb_gadget *, int is_active);

int (*vbus_draw) (struct usb_gadget *, unsigned mA);

int (*pullup) (struct usb_gadget *, int is_on);

int (*ioctl)(struct usb_gadget *, unsigned code, unsigned long param);

};

3. USB Gadget driver对象

struct usb_gadget_driver {

char *function; //驱动名称

enum usb_device_speed speed; //USB设备速度类型

int (*bind)(struct usb_gadget *); //将驱动和设备绑定,一般在驱动注册时调用

void (*unbind)(struct usb_gadget *);//卸载驱动时调用,rmmod时调用

int (*setup)(struct usb_gadget *, const struct usb_ctrlrequest *); //处理ep0的控制请求,在中断中调用,不能睡眠

void (*disconnect)(struct usb_gadget *); //可能在中断中调用不能睡眠

void (*suspend)(struct usb_gadget *); //电源管理模式相关,设备挂起

void (*resume)(struct usb_gadget *);//电源管理模式相关,设备恢复

/* FIXME support safe rmmod */

struct device_driver driver; //内核设备管理使用

};

4. 描述一个I/O请求

struct usb_request {

void *buf; //数据缓存区

unsigned length; //数据长度

dma_addr_t dma; //与buf关联的DMA地址,DMA传输时使用

unsigned no_interrupt:1;//当为true时,表示没有完成函数,则通过中断通知传输完成,这个由DMA控制器直接控制

unsigned zero:1; //当输出的最后一个数据包不够长度是是否填充0

unsigned short_not_ok:1; //当接收的数据不够指定长度时,是否报错

void (*complete)(struct usb_ep *ep, struct usb_request *req);//请求完成函数

void *context;//被completion回调函数使用

struct list_head list; //被Gadget Driver使用,插入队列

int status;//返回完成结果,0表示成功

unsigned actual;//实际传输的数据长度

};

5. 端点

struct usb_ep {

void *driver_data;  //端点私有数据

const char *name; //端点名称

const struct usb_ep_ops *ops; //端点操作函数集

struct list_head ep_list; //Gadget设备建立所有端点的链表

unsigned maxpacket:16;//这个端点使用的最大包长度

};

6. 端点操作函数集

struct usb_ep_ops {

int (*enable) (struct usb_ep *ep,  const struct usb_endpoint_descriptor *desc);

int (*disable) (struct usb_ep *ep);

struct usb_request *(*alloc_request) (struct usb_ep *ep, gfp_t gfp_flags);

void (*free_request) (struct usb_ep *ep, struct usb_request *req);

int (*queue) (struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags);

int (*dequeue) (struct usb_ep *ep, struct usb_request *req);

int (*set_halt) (struct usb_ep *ep, int value);

int (*set_wedge) (struct usb_ep *ep);

int (*fifo_status) (struct usb_ep *ep);

void (*fifo_flush) (struct usb_ep *ep);

};

7. 字符串结构

struct usb_gadget_strings {

u16 language; /* 0x0409 for en-us */

struct usb_string *strings;

};

struct usb_string {

u8 id; //索引

const char *s;

};

8. UDC驱动程序需要实现的上层调用接口

int usb_gadget_register_driver(struct usb_gadget_driver *driver);

int usb_gadget_unregister_driver(struct usb_gadget_driver *driver);

三、UDC驱动程序

1. UDC层主要数据结构,以S3C2410为例,在driver/usb/gadget/s3c2410_udc.c和s3c2410_udc.h文件中。

下面的结构基本上每个UDC驱动程序都会实现,但具体实现的细节又不太相同。但万变不离其宗,宗就是上面介绍的基本gadget驱动数据结构,基本上UDC驱动程序自己实现的数据结构都是都这些基本数据结构的二次封装。

a. 设备结构

struct s3c2410_udc {

           spinlock_t lock;

          struct s3c2410_ep ep[S3C2410_ENDPOINTS];

          int address;

           struct usb_gadget gadget;

           struct usb_gadget_driver *driver;

          struct s3c2410_request fifo_req;

           u8 fifo_buf[EP_FIFO_SIZE];

           u16 devstatus;

           u32 port_status;

          int ep0state;

           unsigned got_irq : 1;

           unsigned req_std : 1;

           unsigned req_config : 1;

           unsigned req_pending : 1;

           u8 vbus;

        struct dentry *regs_info;

};

程序中对这个结构的初始化:

static struct s3c2410_udc memory = {

.gadget = {

           .ops = &s3c2410_ops,

                     .ep0 = &memory.ep[0].ep,

                    .name = gadget_name,

                     .dev = {

                                .init_name = "gadget",

                     },

},

/* control endpoint */

.ep[0] = { //struct s3c2410_ep

             .num = 0,

            .ep = {//struct usb_ep

                       .name = ep0name,

                       .ops = &s3c2410_ep_ops,

                       .maxpacket = EP0_FIFO_SIZE,

                              },

              .dev = &memory,

            },

/* first group of endpoints */

.ep[1] = {

               .num = 1,

               .ep = {

                           .name = "ep1-bulk",

                           .ops = &s3c2410_ep_ops,

                          .maxpacket = EP_FIFO_SIZE,

                         },

               .dev = &memory,

               .fifo_size = EP_FIFO_SIZE,

               .bEndpointAddress = 1,

               .bmAttributes = USB_ENDPOINT_XFER_BULK,

             },

.ep[2] = {

                   .num = 2,

                  .ep = {

                             .name = "ep2-bulk",

                             .ops = &s3c2410_ep_ops,

                             .maxpacket = EP_FIFO_SIZE,

                      },

                    .dev = &memory,

                    .fifo_size = EP_FIFO_SIZE,

                    .bEndpointAddress = 2,

                    .bmAttributes = USB_ENDPOINT_XFER_BULK,

             },

.ep[3] = {

                .num = 3,

                .ep = {

                          .name = "ep3-bulk",

                           .ops = &s3c2410_ep_ops,

                           .maxpacket = EP_FIFO_SIZE,

                        },

                  .dev = &memory,

                  .fifo_size = EP_FIFO_SIZE,

                  .bEndpointAddress = 3,

                  .bmAttributes = USB_ENDPOINT_XFER_BULK,

                         },

.ep[4] = {

               .num = 4,

               .ep = {

                           .name = "ep4-bulk",

                           .ops = &s3c2410_ep_ops,

                         .maxpacket = EP_FIFO_SIZE,

                      },

               .dev = &memory,

               .fifo_size = EP_FIFO_SIZE,

               .bEndpointAddress = 4,

                .bmAttributes = USB_ENDPOINT_XFER_BULK,

             }

};

不同的UDC,自定义的数据结构不同。但一般都有这样一个数据结构来表示UDC设备,对usb_gadget设备对象进行封装,并包含设备的所有端点。

b. 端点结构

struct s3c2410_ep {

struct list_head queue;

unsigned long last_io; /* jiffies timestamp */

struct usb_gadget *gadget;

struct s3c2410_udc *dev;

const struct usb_endpoint_descriptor *desc;

struct usb_ep ep; //封装的struct usb_ep结构

u8 num;

unsigned short fifo_size;

u8 bEndpointAddress;

u8 bmAttributes;

unsigned halted : 1;

unsigned already_seen : 1;

unsigned setup_stage : 1;

};

对usb_ep结构进行封装,并有一个queue队列来对该端口上的request进行排队。

c. Request结构

struct s3c2410_request {

struct list_head queue; /* ep's requests */

struct usb_request req;

};

对usb_request进行封装,queue变量进行队列排队。

USB设备驱动程序-USB Gadget Driver(二)

装载自:

http://blog.chinaunix.net/uid-14518381-id-3921457.html

1. UDC驱动是作为platform driver向platform子系统注册的,因此UDC驱动首先就需要实现struct platform_driver结构中的函数成员:

struct platform_driver {

           int (*probe)(struct platform_device *); //驱动和设备绑定

           int (*remove)(struct platform_device *); //支持热插拔的设备移除

           void (*shutdown)(struct platform_device *); //设备关闭

           int (*suspend)(struct platform_device *, pm_message_t state); //电源管理相关,挂起设备

           int (*resume)(struct platform_device *); //电源管理相关,恢复设备

           struct device_driver driver;

          struct platform_device_id *id_table; //驱动和设备匹配信息

};

在下面的源码分析中以s3c2410_udc.c文件为例:

static struct platform_driver udc_driver_2410 = {

          .driver = {

                          .name = "s3c2410-usbgadget",

                          .owner = THIS_MODULE,

          },

        .probe = s3c2410_udc_probe,

        .remove = s3c2410_udc_remove,

        .suspend = s3c2410_udc_suspend,

        .resume = s3c2410_udc_resume,

};

其中以s3c2410_udc_probe和s3c2410_udc_remove函数最为重要,s3c2410_udc_probe函数实现驱动和设备的匹配绑定,并分配资源;而s3c2410_udc_remove函数实现资源的释放。

static int s3c2410_udc_probe(struct platform_device *pdev)

{

        struct s3c2410_udc *udc = &memory; //s3c2410的UDC设备,在其中对usb_gadget设备对象和端点等进行了初始化

       struct device *dev = &pdev->dev;

       int retval;

      int irq;

     //获取总线时钟并使能

       usb_bus_clock = clk_get(NULL, "usb-bus-gadget");

        clk_enable(usb_bus_clock);

     //获取设备时钟并使能

      udc_clock = clk_get(NULL, "usb-device");

       clk_enable(udc_clock);

      mdelay(10);

       spin_lock_init (&udc->lock); //初始化设备的自旋锁

       udc_info = pdev->dev.platform_data;

        rsrc_start = S3C2410_PA_USBDEV; //s3c2410 UDC端口起始地址

       rsrc_len = S3C24XX_SZ_USBDEV; //s3c2410端口地址长度

      if (!request_mem_region(rsrc_start, rsrc_len, gadget_name)) //申请端口资源

                   return -EBUSY;

     base_addr = ioremap(rsrc_start, rsrc_len); //端口映射

      if (!base_addr) {

                 retval = -ENOMEM;

                 goto err_mem;

      }

      device_initialize(&udc->gadget.dev); //初始化device设备对象

      udc->gadget.dev.parent = &pdev->dev; //当前UDC设备的父设备对象是platform_device

    udc->gadget.dev.dma_mask = pdev->dev.dma_mask;

       the_controller = udc;

    platform_set_drvdata(pdev, udc); //驱动和设备绑定,在platform_device结构中保存udc设备对象

/*重新初始化设备*/

s3c2410_udc_disable(udc);

s3c2410_udc_reinit(udc);

/* irq setup after old hardware state is cleaned up */

/*申请中断,并绑定中断函数,中断函数是UDC功能驱动的入口函数*/

retval = request_irq(IRQ_USBD, s3c2410_udc_irq, IRQF_DISABLED, gadget_name, udc);

if (udc_info && udc_info->vbus_pin > 0) {

          retval = gpio_request(udc_info->vbus_pin, "udc vbus");

                    irq = gpio_to_irq(udc_info->vbus_pin);

                    retval = request_irq(irq, s3c2410_udc_vbus_irq, IRQF_DISABLED | IRQF_TRIGGER_RISING| IRQF_TRIGGER_FALLING | IRQF_SHARED,gadget_name, udc);

               }

          else {

                   udc->vbus = 1;

}

if (s3c2410_udc_debugfs_root) { //创建虚拟文件debugfs

          udc->regs_info = debugfs_create_file("registers", S_IRUGO, s3c2410_udc_debugfs_root,udc, &s3c2410_udc_debugfs_fops);

          if (!udc->regs_info)

                      dev_warn(dev, "debugfs file creation failed\n");

}

dev_dbg(dev, "probe ok\n");

return 0;

err_gpio_claim:

                   if (udc_info && udc_info->vbus_pin > 0)

              gpio_free(udc_info->vbus_pin);

err_int:

        free_irq(IRQ_USBD, udc);

err_map:

          iounmap(base_addr);

err_mem:

          release_mem_region(rsrc_start, rsrc_len);

return retval;

}

从s3c2410_udc_probe函数可以看出,probe函数主要完成的就是将platform_device设备对象和UDC设备对象建立关系,UDC设备和驱动的一些初始化工作,并申请驱动所需的资源,若端口区间、中断号等,并将中断函数和中断号绑定。这个中断处理函数非常重要,对这个设备的所有操作都将从中断函数入口。

static int s3c2410_udc_remove(struct platform_device *pdev)

{

             struct s3c2410_udc *udc = platform_get_drvdata(pdev); //获取UDC设备对象,在probe函数中绑定的

             unsigned int irq;

            if (udc->driver) //设备的驱动usb_gadget_driver对象,说明设备正在使用

                       return -EBUSY;

          debugfs_remove(udc->regs_info); //移除debugfs文件系统中建立的文件

          if (udc_info && udc_info->vbus_pin > 0) {

                          irq = gpio_to_irq(udc_info->vbus_pin);

                          free_irq(irq, udc);

                    }

           free_irq(IRQ_USBD, udc); //释放中断

           /*释放端口资源*/

           iounmap(base_addr);

           release_mem_region(rsrc_start, rsrc_len);

          /*解除绑定*/

           platform_set_drvdata(pdev, NULL);

          /*释放时钟*/

            if (!IS_ERR(udc_clock) && udc_clock != NULL) {

                              clk_disable(udc_clock);

                               clk_put(udc_clock);

                              udc_clock = NULL;

             }

             if (!IS_ERR(usb_bus_clock) && usb_bus_clock != NULL) {

                                clk_disable(usb_bus_clock);

                                 clk_put(usb_bus_clock);

                                  usb_bus_clock = NULL;

            }

          dev_dbg(&pdev->dev, "%s: remove ok\n", __func__);

          return 0;

}

可以看出,remove函数基本上是probe函数的逆操作,将probe函数中申请的资源释放掉。

2. UDC驱动程序还需要为上层实现usb_gadget_register_driver和usb_gadget_unregister_driver两个gadget driver注册接口,这两个函数将实现gadget driver和udc driver绑定。

int usb_gadget_register_driver(struct usb_gadget_driver *driver)

{

          struct s3c2410_udc *udc = the_controller; //UDC设备对象

           int retval;

         /* Sanity checks */

          if (!udc)

                      return -ENODEV;

        /*UDC设备只能绑定一个gadget driver对象*/

         if (udc->driver)

                   return -EBUSY;

         /*检查gadget driver是否实现了绑定函数、setup函数。同时低速设备也不支持gadget driver*/

        if (!driver->bind || !driver->setup|| driver->speed < USB_SPEED_FULL) {

                   printk(KERN_ERR "Invalid driver: bind %p setup %p speed %d\n",driver->bind, driver->setup, driver->speed);

                    return -EINVAL;

         }

         //支持卸载的话,还需要实现unbind函数

         #if defined(MODULE)

                   if (!driver->unbind) {

                                  printk(KERN_ERR "Invalid driver: no unbind method\n");

                                   return -EINVAL;

                    }

         #endif

         /* Hook the driver */

        /*将gadget driver和udc设备绑定*/

           udc->driver = driver;

           udc->gadget.dev.driver = &driver->driver;

           /* Bind the driver */

           if ((retval = device_add(&udc->gadget.dev)) != 0) { //完成gadget设备在内核中的注册

                         printk(KERN_ERR "Error in device_add() : %d\n",retval);

                        goto register_error;

           }

          if ((retval = driver->bind (&udc->gadget)) != 0) {//gadget驱动绑定函数

                    device_del(&udc->gadget.dev);

                     goto register_error;

         }

         /* Enable udc */

         //使能UDC设备

         s3c2410_udc_enable(udc);

          return 0;

         register_error:

                   udc->driver = NULL;

                   udc->gadget.dev.driver = NULL;

                   return retval;

}

/*gadget 驱动注销函数*/

int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)

{

             struct s3c2410_udc *udc = the_controller;

             if (!udc)

                        return -ENODEV;

             /*驱动必须和注册时的驱动是一致的,同时实现了unbind函数*/

             if (!driver || driver != udc->driver || !driver->unbind)

                        return -EINVAL;

             dprintk(DEBUG_NORMAL,"usb_gadget_register_driver() '%s'\n", driver->driver.name);

             /*调用gadget driver实现的unbind函数*/

driver->unbind(&udc->gadget);

             device_del(&udc->gadget.dev); //和register函数中的device_add对应

             udc->driver = NULL;//这个很重要,这里说明gadget驱动注销之后,才能移除udc设备

             /* Disable udc */

             /*关闭设备*/

               s3c2410_udc_disable(udc);

               return 0;

  }

3. 中断函数

中断处理函数是UDC驱动层的核心函数,由于UDC是从设备,主机端是控制端,所有的操作都是主机端发起的,所以中断处理函数是UDC驱动层的核心函数。

static irqreturn_t s3c2410_udc_irq(int dummy, void *_dev)

{

              struct s3c2410_udc *dev = _dev; //UDC设备对象

              int usb_status;

              int usbd_status;

              int pwr_reg;

              int ep0csr;

              int i;

              u32 idx;

             unsigned long flags;

             spin_lock_irqsave(&dev->lock, flags);

              /* Driver connected ? */

             if (!dev->driver) { //还没有和驱动绑定,清除中断

                                            /* Clear interrupts */

                                         udc_write(udc_read(S3C2410_UDC_USB_INT_REG), S3C2410_UDC_USB_INT_REG);

                                          udc_write(udc_read(S3C2410_UDC_EP_INT_REG),S3C2410_UDC_EP_INT_REG);

                   }

             /* Save index */

              idx = udc_read(S3C2410_UDC_INDEX_REG); //这是哪个端点产生中断的索引号

               /* Read status registers */

             usb_status = udc_read(S3C2410_UDC_USB_INT_REG); //UDC设备状态

              usbd_status = udc_read(S3C2410_UDC_EP_INT_REG); //UDC产生中断的端点状态

               pwr_reg = udc_read(S3C2410_UDC_PWR_REG);

                udc_writeb(base_addr, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG);

               ep0csr = udc_read(S3C2410_UDC_IN_CSR1_REG);

              dprintk(DEBUG_NORMAL, "usbs=%02x, usbds=%02x, pwr=%02x ep0csr=%02x\n", usb_status, usbd_status, pwr_reg, ep0csr);

               /*

开始中断的实际处理,这里的中断只有两种类型:

                   1. UDC设备中断: 重置、挂起和恢复

                   2. 端点中断

                */

                /* UDC设备RESET操作处理 */

              if (usb_status & S3C2410_UDC_USBINT_RESET) { //Reset中断

                                  /* two kind of reset :

                                   * - reset start -> pwr reg = 8

                                   * - reset end -> pwr reg = 0

                                   **/

                                  dprintk(DEBUG_NORMAL, "USB reset csr %x pwr %x\n", ep0csr, pwr_reg);

                                   dev->gadget.speed = USB_SPEED_UNKNOWN;

                                   udc_write(0x00, S3C2410_UDC_INDEX_REG);

                                   udc_write((dev->ep[0].ep.maxpacket & 0x7ff) >> 3, S3C2410_UDC_MAXP_REG);

                                   dev->address = 0;

                                   dev->ep0state = EP0_IDLE;

                                    dev->gadget.speed = USB_SPEED_FULL;

                                     /* clear interrupt */

                                    udc_write(S3C2410_UDC_USBINT_RESET, S3C2410_UDC_USB_INT_REG);

                                    udc_write(idx, S3C2410_UDC_INDEX_REG);

                                     spin_unlock_irqrestore(&dev->lock, flags);

                                   return IRQ_HANDLED;

                }

              /* UDC设备RESUME操作处理 */

               if (usb_status & S3C2410_UDC_USBINT_RESUME) { //Resume中断

                                               dprintk(DEBUG_NORMAL, "USB resume\n");

                                               /* clear interrupt */

                                               udc_write(S3C2410_UDC_USBINT_RESUME, S3C2410_UDC_USB_INT_REG);

                                             if (dev->gadget.speed != USB_SPEED_UNKNOWN && dev->driver&& dev->driver->resume)//调用resume函数

dev->driver->resume(&dev->gadget);

               }

               /* UDC设备SUSPEND操作处理 */

             if (usb_status & S3C2410_UDC_USBINT_SUSPEND) { //Suspend中断

                                         dprintk(DEBUG_NORMAL, "USB suspend\n");

                                        /* clear interrupt */

                                       udc_write(S3C2410_UDC_USBINT_SUSPEND, S3C2410_UDC_USB_INT_REG);

                                       if (dev->gadget.speed != USB_SPEED_UNKNOWN && dev->driver&& dev->driver->suspend)//调用suspend函数

dev->driver->suspend(&dev->gadget);

                                       dev->ep0state = EP0_IDLE;

               }

             /* 下面就是端点中断得处理 */

             /* 首先是控制端点(端点0)的中断处理*/

              /* check on ep0csr != 0 is not a good idea as clearing in_pkt_ready

                      * generate an interrupt

               */

                if (usbd_status & S3C2410_UDC_INT_EP0) { //端点0的中断

                                           dprintk(DEBUG_VERBOSE, "USB ep0 irq\n");

                                            /* Clear the interrupt bit by setting it to 1 */

                                           udc_write(S3C2410_UDC_INT_EP0, S3C2410_UDC_EP_INT_REG);

                                            s3c2410_udc_handle_ep0(dev); //处理端点0

                 }

                /* 其他端点,就是数据传输的处理*/

                for (i = 1; i < S3C2410_ENDPOINTS; i++) { //遍历所有端点,找出中断的端点

                                           u32 tmp = 1 << i;

                                           if (usbd_status & tmp) {

                                                                             dprintk(DEBUG_VERBOSE, "USB ep%d irq\n", i);

                                                                              /* Clear the interrupt bit by setting it to 1 */

                                                                               udc_write(tmp, S3C2410_UDC_EP_INT_REG);

                                                                             s3c2410_udc_handle_ep(&dev->ep[i]); //处理对应端点

                                                }

                           }

                dprintk(DEBUG_VERBOSE, "irq: %d s3c2410_udc_done.\n", IRQ_USBD);

                /* Restore old index */

                 udc_write(idx, S3C2410_UDC_INDEX_REG);

                 spin_unlock_irqrestore(&dev->lock, flags);

                  return IRQ_HANDLED;

}

4.  端点操作函数

端点操作函数是UDC驱动的基础,因为大部分的动作其实都是和端点相关的,如数据传输等。

首先来看中断函数中涉及的两个函数,一个是端点0的处理函数,一个是其他端点的处理函数。

static void s3c2410_udc_handle_ep0(struct s3c2410_udc *dev)

{

               u32 ep0csr;

              struct s3c2410_ep *ep = &dev->ep[0];

               struct s3c2410_request *req;

               struct usb_ctrlrequest crq;

               if (list_empty(&ep->queue))

                            req = NULL;

              else

                          req = list_entry(ep->queue.next, struct s3c2410_request, queue);

             /* We make the assumption that S3C2410_UDC_IN_CSR1_REG equal to

               * S3C2410_UDC_EP0_CSR_REG when index is zero */

              udc_write(0, S3C2410_UDC_INDEX_REG);

            ep0csr = udc_read(S3C2410_UDC_IN_CSR1_REG);

            dprintk(DEBUG_NORMAL, "ep0csr %x ep0state %s\n", ep0csr, ep0states[dev->ep0state]);

            /* clear stall status */

             if (ep0csr & S3C2410_UDC_EP0_CSR_SENTSTL) { //清除STALL状态

                              s3c2410_udc_nuke(dev, ep, -EPIPE);

                              dprintk(DEBUG_NORMAL, "... clear SENT_STALL ...\n");

                              s3c2410_udc_clear_ep0_sst(base_addr);

                               dev->ep0state = EP0_IDLE;

                                             return;

                 }

                /* clear setup end */

                if (ep0csr & S3C2410_UDC_EP0_CSR_SE) {

                              dprintk(DEBUG_NORMAL, "... serviced SETUP_END ...\n");

                              s3c2410_udc_nuke(dev, ep, 0);

                               s3c2410_udc_clear_ep0_se(base_addr);

                                dev->ep0state = EP0_IDLE;

                 }

                /*端点0的状态处理*/

               switch (dev->ep0state) {

                               case EP0_IDLE:

                                                s3c2410_udc_handle_ep0_idle(dev, ep, &crq, ep0csr); //在这个函数中会调用上层提供的gadget_driver中实现的setup函数来处理控制数据包

                                                break;

                                case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR etc */ //向主机发送数据

                                              dprintk(DEBUG_NORMAL, "EP0_IN_DATA_PHASE ... what now?\n");

                                              if (!(ep0csr & S3C2410_UDC_EP0_CSR_IPKRDY) && req) {

                                                                s3c2410_udc_write_fifo(ep, req); //写UDC FIFO

                                              }

                                               break;

                               case EP0_OUT_DATA_PHASE: /* SET_DESCRIPTOR etc */ //从主机接收数据

                                              dprintk(DEBUG_NORMAL, "EP0_OUT_DATA_PHASE ... what now?\n");

                                               if ((ep0csr & S3C2410_UDC_EP0_CSR_OPKRDY) && req ) {

                                                                  s3c2410_udc_read_fifo(ep,req);

                                              }

                                              break;

                              case EP0_END_XFER:

                                             dprintk(DEBUG_NORMAL, "EP0_END_XFER ... what now?\n");

                                             dev->ep0state = EP0_IDLE;

                                               break;

                            case EP0_STALL:

                                        dprintk(DEBUG_NORMAL, "EP0_STALL ... what now?\n");

                                       dev->ep0state = EP0_IDLE;

                                        break;

            }

}

/*

* handle_ep - Manage I/O endpoints

其他端点的处理函数,主要是数据发送和接收

*/

static void s3c2410_udc_handle_ep(struct s3c2410_ep *ep)

{

                       struct s3c2410_request *req;

                       int is_in = ep->bEndpointAddress & USB_DIR_IN;

                       u32 ep_csr1;

                       u32 idx;

                     if (likely (!list_empty(&ep->queue))) //取出申请

                                         req = list_entry(ep->queue.next, struct s3c2410_request, queue);

                     else

                                      req = NULL;

                   idx = ep->bEndpointAddress & 0x7F; //端点地址

                  if (is_in) { //向主机发送数据

                                     udc_write(idx, S3C2410_UDC_INDEX_REG);

                                    ep_csr1 = udc_read(S3C2410_UDC_IN_CSR1_REG);

                                    dprintk(DEBUG_VERBOSE, "ep%01d write csr:%02x %d\n", idx, ep_csr1, req ? 1 : 0);

                                    if (ep_csr1 & S3C2410_UDC_ICSR1_SENTSTL) {

                                                      dprintk(DEBUG_VERBOSE, "st\n");

                                                       udc_write(idx, S3C2410_UDC_INDEX_REG);

                                                       udc_write(ep_csr1 & ~S3C2410_UDC_ICSR1_SENTSTL, S3C2410_UDC_IN_CSR1_REG);

                                                                 return;

                                     }

                                   if (!(ep_csr1 & S3C2410_UDC_ICSR1_PKTRDY) && req) {

s3c2410_udc_write_fifo(ep,req);

                                    }

                  }

                 else  //从主机接收数据

                   {

                                  udc_write(idx, S3C2410_UDC_INDEX_REG);

                                  ep_csr1 = udc_read(S3C2410_UDC_OUT_CSR1_REG);

                                   dprintk(DEBUG_VERBOSE, "ep%01d rd csr:%02x\n", idx, ep_csr1);

                                  if (ep_csr1 & S3C2410_UDC_OCSR1_SENTSTL) {

                                                         udc_write(idx, S3C2410_UDC_INDEX_REG);

                                                         udc_write(ep_csr1 & ~S3C2410_UDC_OCSR1_SENTSTL, S3C2410_UDC_OUT_CSR1_REG);

                                                         return;

                                    }

                                 if ((ep_csr1 & S3C2410_UDC_OCSR1_PKTRDY) && req) {

s3c2410_udc_read_fifo(ep,req);

                                 }

                 }

}

端点操作函数集:端点的基本操作函数

static const struct usb_ep_ops s3c2410_ep_ops = {

                        .enable = s3c2410_udc_ep_enable, //端点使能

                        .disable = s3c2410_udc_ep_disable, //关闭端点

                        .alloc_request = s3c2410_udc_alloc_request, //分配一个请求

                          .free_request = s3c2410_udc_free_request, //释放请求

                              .queue = s3c2410_udc_queue, //向端点提交一个请求

                            .dequeue = s3c2410_udc_dequeue, //从端点请求队列中删除一个请求

                          .set_halt = s3c2410_udc_set_halt,

};

主要分析queue这个函数,因为上层主要和这个函数打交道,接收或发送数据都需要对应的端点队列提交请求

            static int s3c2410_udc_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)

{

                  struct s3c2410_request *req = to_s3c2410_req(_req);

                  struct s3c2410_ep *ep = to_s3c2410_ep(_ep);

                   struct s3c2410_udc *dev;

                   u32 ep_csr = 0;

                  int fifo_count = 0;

                     unsigned long flags;

                 if (unlikely (!_ep || (!ep->desc && ep->ep.name != ep0name))) {

                                dprintk(DEBUG_NORMAL, "%s: invalid args\n", __func__);

                                return -EINVAL;

                   }

                dev = ep->dev;

                if (unlikely (!dev->driver|| dev->gadget.speed == USB_SPEED_UNKNOWN)) {

                                  return -ESHUTDOWN;

                    }

                local_irq_save (flags);

               if (unlikely(!_req || !_req->complete || !_req->buf || !list_empty(&req->queue))) {

                              if (!_req)

                                            dprintk(DEBUG_NORMAL, "%s: 1 X X X\n", __func__);

                              else {

                                            dprintk(DEBUG_NORMAL, "%s: 0 %01d %01d %01d\n", __func__, !_req->complete,!_req->buf, !list_empty(&req->queue));

                                    }

                              local_irq_restore(flags);

                             return -EINVAL;

                }

              _req->status = -EINPROGRESS;

              _req->actual = 0;

            dprintk(DEBUG_VERBOSE, "%s: ep%x len %d\n", __func__, ep->bEndpointAddress, _req->length);

            if (ep->bEndpointAddress) { //设置端点号

                            udc_write(ep->bEndpointAddress & 0x7F, S3C2410_UDC_INDEX_REG);

                           ep_csr = udc_read((ep->bEndpointAddress & USB_DIR_IN) ? S3C2410_UDC_IN_CSR1_REG: S3C2410_UDC_OUT_CSR1_REG);

                            fifo_count = s3c2410_udc_fifo_count_out();

           }

          else

            {

                           udc_write(0, S3C2410_UDC_INDEX_REG);

                            ep_csr = udc_read(S3C2410_UDC_IN_CSR1_REG);

                             fifo_count = s3c2410_udc_fifo_count_out();

             }

            /* kickstart this i/o queue? */

              if (list_empty(&ep->queue) && !ep->halted) { //该端点队列为空,且没有关闭,则直接完成申请

                                     if (ep->bEndpointAddress == 0 /* ep0 */) { //端点0

                                                               switch (dev->ep0state) {

                                                                               case EP0_IN_DATA_PHASE:

                                                                                             if (!(ep_csr&S3C2410_UDC_EP0_CSR_IPKRDY) &&s3c2410_udc_write_fifo(ep,req)) {

                                                                                                                   dev->ep0state = EP0_IDLE;

                                                                                                                     req = NULL;

                                                                                                }

                                                                                                break;

                                                                                case EP0_OUT_DATA_PHASE:

                                                                                                if ((!_req->length)|| ((ep_csr & S3C2410_UDC_OCSR1_PKTRDY)&& s3c2410_udc_read_fifo(ep,req))) {

                                                                                                                               dev->ep0state = EP0_IDLE;

                                                                                                                       req = NULL;

                                                                                                 }

                                                                                                break;

                                                                                default:

                                                                                               local_irq_restore(flags);

                                                                                               return -EL2HLT;

                                                                  }

                                        }

                                      else if ((ep->bEndpointAddress & USB_DIR_IN) != 0 && (!(ep_csr&S3C2410_UDC_OCSR1_PKTRDY)) &&s3c2410_udc_write_fifo(ep, req)) {

                                                               req = NULL;

                                     }

                                      else if ((ep_csr & S3C2410_UDC_OCSR1_PKTRDY)&& fifo_count&& s3c2410_udc_read_fifo(ep, req)) {

                                                               req = NULL;

                                      }

                }

           /* pio or dma irq handler advances the queue. */

            if (likely (req != 0))

                            list_add_tail(&req->queue, &ep->queue); //加入队列,等待中断处理

            local_irq_restore(flags);

            dprintk(DEBUG_VERBOSE, "%s ok\n", __func__);

            return 0;

}

          UDC驱动中还有一个重要函数,在数据传输或接收完成,即一个请求完成之后,会调用这个请求的完成函数来通知上层驱动。

static void s3c2410_udc_done(struct s3c2410_ep *ep,  struct s3c2410_request *req,  int status) 

             unsigned halted = ep->halted;

             list_del_init(&req->queue);  //将这个请求从端点请求队列中删除

             if (likely (req->req.status == -EINPROGRESS)) 
                           req->req.status = status; //返回完成状态
            else 
                           status = req->req.status;

            ep->halted = 1; 
req->req.complete(&ep->ep, &req->req); 
            ep->halted = halted; 
}

总结:

       UDC设备驱动层的源码就分析得差不多了,其他很多函数都是操作寄存器,与UDC设备密切相关,但总的来说完成的功能都是一致的。可以发现,在UDC设备驱动层主要需要做以下几个工作:

1. 对usb_gadget、usb_ep、usb_request三个标准数据结构进行封装,根据自己UDC的一些设备特性,设计对应的自己的数据结构;

2. 实现platform_driver数据结构中的函数,将UDC设备驱动向platform系统进行注册;

3. 实现usb_gadget_ops函数集,这些函数主要是操作UDC设备的一些特性(针对设备);

4. 实现usb_ep_ops函数集,这些函数主要是操作端点的功能,如请求分配和提交等(针对端点);

5. 实现UDC设备的中断处理函数,这个函数基本上就是UDC设备驱动的核心;

6.实现上层功能驱动注册接口函数:

     int usb_gadget_register_driver(struct usb_gadget_driver *driver)

     int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)

USB设备驱动程序-USB Gadget Driver(三)

Gadget设备层

这一层是可选的,介于UDC驱动层和Gadget功能层之间。主要源码在composite.c和composite.h文件中,设备层其实和硬件无关,主要实现一些通用性的代码,减少gadget功能层的代码重复工作。Gadget设备层其中承上启下的作用,联系Gadget功能层和UDC驱动层。

将composite源码独立出来,还为复合设备的实现提供了一个通用性的框架。复合设备是指在一个配置描述符中支持多个功能,或者支持多个配置的设备中,每个配置都有一个不同的功能。如一个设备同时支持网络和存储,一个设备同时支持键盘和鼠标功能等。

Gadget设备层的主要数据结构:

1. Function

struct usb_function { //描述一个配置的一个功能

                                    const char *name; //功能名称

                                    struct usb_gadget_strings **strings; //string数组,通过bind中分配的id访问

                                    struct usb_descriptor_header **descriptors; //全速和低速的描述符表,用于bind中分配的接口描述符和string描述符

                                    struct usb_descriptor_header **hs_descriptors; //高速描述符表

                                     struct usb_configuration *config;//调用usb_add_function()函数赋值,是这个功能关联的配置描述符

                                   /* REVISIT: bind() functions can be marked __init, which

                                     * makes trouble for section mismatch analysis. See if

                                     * we can't restructure things to avoid mismatching.

                                     * Related: unbind() may kfree() but bind() won't...

                                    */

                                    /* configuration management: bind/unbind */

                                     /*在Gadget注册时,分配资源*/

                                   int (*bind)(struct usb_configuration *, struct usb_function *);

                                    /*unbind的逆操作*/

                                    void (*unbind)(struct usb_configuration *, struct usb_function *);

                                   /* runtime state management */

                                   /*重新配置altsetting,*/

                                   int (*set_alt)(struct usb_function *,unsigned interface, unsigned alt);

                                   /*获取当前altsetting*/

                                   int (*get_alt)(struct usb_function *, unsigned interface);

                                     /*关闭功能*/

                                     void (*disable)(struct usb_function *);

                                     /*接口相关的控制处理*/

                                    int (*setup)(struct usb_function *, const struct usb_ctrlrequest *);

                                    /*电源管理相关的挂起和恢复功能*/

                                    void (*suspend)(struct usb_function *);

                                    void (*resume)(struct usb_function *);

                                   /* private: */

                                  /* internals */

                                    struct list_head list;

                                     DECLARE_BITMAP(endpoints, 32);

};

2. Config

struct usb_configuration { //表示一个Gadget配置

                                              const char *label; //配置名称

                                   struct usb_gadget_strings **strings; //字符串表

                                    const struct usb_descriptor_header **descriptors; //功能描述符表

                                      /* REVISIT: bind() functions can be marked __init, which

                                        * makes trouble for section mismatch analysis. See if

                                         * we can't restructure things to avoid mismatching...

                                       */

                                       /* configuration management: bind/unbind */

                                       /*在usb_add_config函数中调用,分配资源等*/

                                        int (*bind)(struct usb_configuration *);

                                        void (*unbind)(struct usb_configuration *);

                                     /*处理驱动框架不能处理的配置控制请求*/

                                          int (*setup)(struct usb_configuration *, const struct usb_ctrlrequest *);

                                     /* fields in the config descriptor */

                                     /*用来赋值配置描述符*/

                                      u8 bConfigurationValue;

                                       u8 iConfiguration;

                                       u8 bmAttributes;

                                       u8 bMaxPower;

                                   /*和composite设备关联,在usb_add_config函数中设置*/

                                      struct usb_composite_dev *cdev;

                                   /* private: */

                                    /* internals */

                                       struct list_head list;

                                        struct list_head functions; //功能链表

                                        u8 next_interface_id;

                                        unsigned highspeed:1;

                                         unsigned fullspeed:1;

                                          struct usb_function *interface[MAX_CONFIG_INTERFACES];

};

3. Driver

struct usb_composite_driver {

                                                  const char *name; //驱动名称

                                                  const struct usb_device_descriptor *dev; //设备描述符

                                                  struct usb_gadget_strings **strings; //字符串表

                                                   /* REVISIT: bind() functions can be marked __init, which

                                                    * makes trouble for section mismatch analysis. See if

                                                    * we can't restructure things to avoid mismatching...

                                                     */

                                                  int (*bind)(struct usb_composite_dev *);

                                                  int (*unbind)(struct usb_composite_dev *);

                                                  /* global suspend hooks */

                                                 void (*suspend)(struct usb_composite_dev *);

                                                 void (*resume)(struct usb_composite_dev *);

};

4. Dev

struct usb_composite_dev { //表示一个composite设备

                                             struct usb_gadget *gadget; //关联的gadget

                                             struct usb_request *req; //用于控制响应,提前分配的

                                              unsigned bufsiz; //req中提前分配的buffer长度

                                             struct usb_configuration *config; //当前配置

                                                        /* private: */

                                              /* internals */

                                            struct usb_device_descriptor desc;

                                            struct list_head configs; //配置链表

                                             struct usb_composite_driver *driver;

                                              u8 next_string_id;

                                           /* the gadget driver won't enable the data pullup

                                             * while the deactivation count is nonzero.

                                            */

                                             unsigned deactivations;

                                             /* protects at least deactivation count */

                                            spinlock_t lock;

};

其中重要的函数:

1.

static int __init composite_bind(struct usb_gadget *gadget)

{

          struct usb_composite_dev *cdev;

           int status = -ENOMEM;

           cdev = kzalloc(sizeof *cdev, GFP_KERNEL); //分配一个composite设备对象

           if (!cdev)

                         return status;

            spin_lock_init(&cdev->lock);

            /*将udc gadget和composite设备建立关系*/

             cdev->gadget = gadget;

            set_gadget_data(gadget, cdev);

             INIT_LIST_HEAD(&cdev->configs); //初始化配置链表

               /* preallocate control response and buffer */

              /*分配控制端点的请求数据结构和缓存区*/

             cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL);

             if (!cdev->req)

                                      goto fail;

             cdev->req->buf = kmalloc(USB_BUFSIZ, GFP_KERNEL); //分配控制请求的缓冲区

            if (!cdev->req->buf)

                                      goto fail;

             cdev->req->complete = composite_setup_complete; //端的0的完成函数

             gadget->ep0->driver_data = cdev;

              cdev->bufsiz = USB_BUFSIZ;

               cdev->driver = composite;

           /*设置gadget的供电模式*/

            usb_gadget_set_selfpowered(gadget);

          /* interface and string IDs start at zero via kzalloc.

           * we force endpoints to start unassigned; few controller

           * drivers will zero ep->driver_data.

           */

           /*将所有端点的driver_data成员清零*/

            usb_ep_autoconfig_reset(cdev->gadget);

         /* composite gadget needs to assign strings for whole device (like

           * serial number), register function drivers, potentially update

           * power state and consumption, etc

          */

         /*调用功能层提供的bind函数,分配资源*/

         status = composite->bind(cdev);

         if (status < 0)

                          goto fail;

          /*设置composite设备的设备描述符*/

          cdev->desc = *composite->dev;

           cdev->desc.bMaxPacketSize0 = gadget->ep0->maxpacket;

          /* standardized runtime overrides for device ID data */

          /*设置composite设备的设备描述符信息*/

         if (idVendor)

                        cdev->desc.idVendor = cpu_to_le16(idVendor);

         if (idProduct)

                       cdev->desc.idProduct = cpu_to_le16(idProduct);

        if (bcdDevice)

                       cdev->desc.bcdDevice = cpu_to_le16(bcdDevice);

         /* strings can't be assigned before bind() allocates the

          * releavnt identifiers

          */

          /*设置字符串描述符*/

        if (cdev->desc.iManufacturer && iManufacturer)

                          string_override(composite->strings, cdev->desc.iManufacturer, iManufacturer);

       if (cdev->desc.iProduct && iProduct)

                         string_override(composite->strings, cdev->desc.iProduct, iProduct);

      if (cdev->desc.iSerialNumber && iSerialNumber)

                          string_override(composite->strings, cdev->desc.iSerialNumber, iSerialNumber);

         INFO(cdev, "%s ready\n", composite->name);

          return 0;

fail:

               composite_unbind(gadget);

              return status;

}

Composite_bind函数主要完成UDC驱动层gadget设备和设备层composite设备关系的建立,分配控制端点0的请求数据结构,设置设备描述符,并调用功能层的bind函数分配功能层所需要的资源等工作。

2.

static void /* __init_or_exit */ composite_unbind(struct usb_gadget *gadget)

{

                  struct usb_composite_dev *cdev = get_gadget_data(gadget);

                             /* composite_disconnect() must already have been called

                    * by the underlying peripheral controller driver!

                     * so there's no i/o concurrency that could affect the

                     * state protected by cdev->lock.

                    */

                    WARN_ON(cdev->config);

                   while ( !list_empty(&cdev->configs)) { //遍历设备的配置链表

                                      struct usb_configuration *c;

                                      c = list_first_entry(&cdev->configs, struct usb_configuration, list);

                                      while (!list_empty(&c->functions)) { //遍历这个配置下的所有功能

                                                             struct usb_function *f;

                                                              f = list_first_entry(&c->functions, struct usb_function, list);

                                                               list_del(&f->list);

                                                              if (f->unbind) { //调用功能的unbind函数

                                                                                     DBG(cdev, "unbind function '%s'/%p\n", f->name, f);

                                                                                     f->unbind(c, f);

                                                                                     /* may free memory for "f" */

                                                                }

                                         }

                                        list_del(&c->list);

                                        if (c->unbind) { //调用配置的unbind函数

                                                            DBG(cdev, "unbind config '%s'/%p\n", c->label, c);

                                                                      c->unbind(c);

                                                            /* may free memory for "c" */

                                          }

                   }

                  if (composite->unbind) //调用功能驱动的unbind函数

                                  composite->unbind(cdev);

                 if (cdev->req) { //释放分配的控制请求的缓存区

                                 kfree(cdev->req->buf);

                                usb_ep_free_request(gadget->ep0, cdev->req);

                 }

                kfree(cdev); //释放composite设备

                set_gadget_data(gadget, NULL);

                composite = NULL;

}

Unbind函数完成bind函数中分配的资源,并遍历所有配置和各个配置下的功能,调用其对应得unbind函数来释放资源。

其他重要函数:

在配置中增加一个功能,每个配置初始化后都必须至少有一个功能。

int __init usb_add_function(struct usb_configuration *config, struct usb_function *function)

{

                 int value = -EINVAL;

                  DBG(config->cdev, "adding '%s'/%p to config '%s'/%p\n",function->name, function,config->label, config);

                  if (!function->set_alt || !function->disable)

                                           goto done;

                  /*设置功能所属配置,并将功能加入配置的链表中*/

                   function->config = config;

                   list_add_tail(&function->list, &config->functions);

                   /* REVISIT *require* function->bind? */

                   if (function->bind) { //调用功能的bind函数

                                                  value = function->bind(config, function); //分配这个功能所需要的资源

                                                   if (value < 0) {

                                                                         list_del(&function->list);

                                                                         function->config = NULL;

                                                   }

                    }

                    else

                                     value = 0;

                   /* We allow configurations that don't work at both speeds.

                     * If we run into a lowspeed Linux system, treat it the same

                     * as full speed ... it's the function drivers that will need

                     * to avoid bulk and ISO transfers.

                      */

                   if (!config->fullspeed && function->descriptors)

                                                   config->fullspeed = true;

                    if (!config->highspeed && function->hs_descriptors)

                                                 config->highspeed = true;

         done:

                   if (value)

                                DBG(config->cdev, "adding '%s'/%p --> %d\n", function->name, function, value);

                  return value;

}

/*为设备增加一个配置,这个实现原理和在配置下增加一个功能类似*/

int __init usb_add_config(struct usb_composite_dev *cdev, struct usb_configuration *config)

{

                 int status = -EINVAL;

                  struct usb_configuration *c;

                  DBG(cdev, "adding config #%u '%s'/%p\n", config->bConfigurationValue, config->label, config);

                  if (!config->bConfigurationValue || !config->bind)

                                            goto done;

                     /* Prevent duplicate configuration identifiers */

                         list_for_each_entry(c, &cdev->configs, list) { //若配置已经加入了,则不需要再加

                                                 if (c->bConfigurationValue == config->bConfigurationValue) {

                                                                                    status = -EBUSY;

                                                                                     goto done;

                                                 }

                            }

                        /*将这个配置加入composite设备*/

                         config->cdev = cdev;

                         list_add_tail(&config->list, &cdev->configs);

                         /*初始化这个配置的功能链表头*/

                         INIT_LIST_HEAD(&config->functions);

                         config->next_interface_id = 0;

                          status = config->bind(config); //完成这个配置的资源分配

                           if (status < 0) {

                                                       list_del(&config->list);

                                                      config->cdev = NULL;

                            }

                            else

                            {

                                                      unsigned i;

                                                      DBG(cdev, "cfg %d/%p speeds:%s%s\n", config->bConfigurationValue, config, config->highspeed ? " high" : "",

                                                              config->fullspeed? (gadget_is_dualspeed(cdev->gadget)? " full": " full/low"): "");

                                                      for (i = 0; i < MAX_CONFIG_INTERFACES; i++) { //查看这个配置下有哪有功能

                                                                                     struct usb_function *f = config->interface[i];

                                                                                     if (!f)

                                                                                             continue;

                                                                                     DBG(cdev, " interface %d = %s/%p\n",i, f->name, f);

                                                         }

                            }

                          /* set_alt(), or next config->bind(), sets up

                            * ep->driver_data as needed.

                          */

                           //将gadget设备的所有端点的driver_data清零

                            usb_ep_autoconfig_reset(cdev->gadget);

              done:

                           if (status)

                                           DBG(cdev, "added config '%s'/%u --> %d\n", config->label, config->bConfigurationValue, status);

                           return status;

}

/*设置一个配置*/

static int set_config(struct usb_composite_dev *cdev, const struct usb_ctrlrequest *ctrl, unsigned number)

{

                   struct usb_gadget *gadget = cdev->gadget;

                   struct usb_configuration *c = NULL;

                    int result = -EINVAL;

                    unsigned power = gadget_is_otg(gadget) ? 8 : 100;

                     int tmp;

                   if (cdev->config)

                                  reset_config(cdev); //清除设备目前使用的配置

                  if (number) { //根据配置值找到对应的配置

                                       list_for_each_entry(c, &cdev->configs, list) {

                                                                          if (c->bConfigurationValue == number) {

                                                                                                        result = 0;

                                                                                                        break;

                                                                          }

                                      }

                                     if (result < 0) //没有找到这个配置

                                                      goto done;

                 }

                else

                          result = 0;

              INFO(cdev, "%s speed config #%d: %s\n", ({ char *speed;

                           switch (gadget->speed) {

                                              case USB_SPEED_LOW: speed = "low"; break;

                                             case USB_SPEED_FULL: speed = "full"; break;

                                            case USB_SPEED_HIGH: speed = "high"; break;

                                             default: speed = "?"; break;

                           } ; speed; }), number, c ? c->label : "unconfigured");

          if (!c)

                      goto done;

         cdev->config = c; //设置为设备使用的配置

        /* Initialize all interfaces by setting them to altsetting zero. */

           for (tmp = 0; tmp < MAX_CONFIG_INTERFACES; tmp++) {

                                struct usb_function *f = c->interface[tmp];

                                struct usb_descriptor_header **descriptors;

                                if (!f)

                                                           break;

                                 /*

                                           * Record which endpoints are used by the function. This is used

                                          * to dispatch control requests targeted at that endpoint to the

                                          * function's setup callback instead of the current

                                          * configuration's setup callback.

                                 */

                                if (gadget->speed == USB_SPEED_HIGH)

                                                      descriptors = f->hs_descriptors;

                                else

                                                      descriptors = f->descriptors;

                               for (; *descriptors; ++descriptors) { //遍历功能的所有端点描述符

                                                     struct usb_endpoint_descriptor *ep;

                                                    int addr;

                                                    if ((*descriptors)->bDescriptorType != USB_DT_ENDPOINT)

                                                                                      continue;

                                                     ep = (struct usb_endpoint_descriptor *)*descriptors;

                                                     addr = ((ep->bEndpointAddress & 0x80) >> 3) | (ep->bEndpointAddress & 0x0f);

                                                   set_bit(addr, f->endpoints); //这个功能采用的端点号

                                  }

                                  result = f->set_alt(f, tmp, 0); //设置功能的默认值

                                 if (result < 0) {

                                                    DBG(cdev, "interface %d (%s/%p) alt 0 --> %d\n", tmp, f->name, f, result);

                                                    reset_config(cdev);

                                                     goto done;

                                   }

                 }

               /* when we return, be sure our power usage is valid */

                        power = c->bMaxPower ? (2 * c->bMaxPower) : CONFIG_USB_GADGET_VBUS_DRAW;

    done:

             usb_gadget_vbus_draw(gadget, power);

              return result;

}

/*这个setup函数完成了ep0所需要处理的而底层不能处理的功能,由于这一个层实现了这个函数,所以功能层就不需要关注于USB协议的这些事务性处理,而重点放在需要实现的功能上*/

static int composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)

{

                  struct usb_composite_dev *cdev = get_gadget_data(gadget); //由gadget获取composite设备

                  struct usb_request *req = cdev->req; //控制请求

                  int value = -EOPNOTSUPP;

                   u16 w_index = le16_to_cpu(ctrl->wIndex);

                    u8 intf = w_index & 0xFF;

                    u16 w_value = le16_to_cpu(ctrl->wValue);

                   u16 w_length = le16_to_cpu(ctrl->wLength);

                 struct usb_function *f = NULL;

                 u8 endp;

               /* partial re-init of the response message; the function or the

                   * gadget might need to intercept e.g. a control-OUT completion

                * when we delegate to it.

                */

              //初始化响应请求信息

                req->zero = 0;

                req->complete = composite_setup_complete;

               req->length = USB_BUFSIZ;

               gadget->ep0->driver_data = cdev;

               switch (ctrl->bRequest) { //根据请求类型处理

                                                 /* we handle all standard USB descriptors */

                                               case USB_REQ_GET_DESCRIPTOR: //返回描述符

                                                                        if (ctrl->bRequestType != USB_DIR_IN)

                                                                                                      goto unknown;

                                                                       switch (w_value >> 8) {

                                                                                                   case USB_DT_DEVICE: //返回设备描述符

                                                                                                                         cdev->desc.bNumConfigurations =  count_configs(cdev, USB_DT_DEVICE);

                                                                                                                          value = min(w_length, (u16) sizeof cdev->desc);

                                                                                                                          memcpy(req->buf, &cdev->desc, value);

                                                                                                                          break;

                                                                                                     case USB_DT_DEVICE_QUALIFIER: //返回qualifier描述符

                                                                                                                          if (!gadget_is_dualspeed(gadget))

                                                                                                                                                           break;

                                                                                                                           device_qual(cdev);

                                                                                                                          value = min_t(int, w_length, sizeof(struct usb_qualifier_descriptor));

                                                                                                                          break;

                                                                                                    case USB_DT_OTHER_SPEED_CONFIG:

                                                                                                                           if (!gadget_is_dualspeed(gadget))

                                                                                                                                                               break;

                                                                                                                            /* FALLTHROUGH */

                                                                                                    case USB_DT_CONFIG: //返回配置描述符

                                                                                                                           value = config_desc(cdev, w_value);

                                                                                                                           if (value >= 0)

                                                                                                                                                  value = min(w_length, (u16) value);

                                                                                                                            break;

                                                                                                      case USB_DT_STRING: //返回字符串描述符

                                                                                                                             value = get_string(cdev, req->buf, w_index, w_value & 0xff);

                                                                                                                             if (value >= 0)

                                                                                                                                                      value = min(w_length, (u16) value);

                                                                                                                             break;

                                                                                       }

                                                                             break;

                                                                             /* any number of configs can work */

                                       case USB_REQ_SET_CONFIGURATION: //设置配置

                                                                            if (ctrl->bRequestType != 0)

                                                                                                      goto unknown;

                                                                           if (gadget_is_otg(gadget)) {

                                                                                                    if (gadget->a_hnp_support)

                                                                                                                          DBG(cdev, "HNP available\n");

                                                                                                     else if (gadget->a_alt_hnp_support)

                                                                                                                          DBG(cdev, "HNP on another port\n");

                                                                                                    else

                                                                                                                         VDBG(cdev, "HNP inactive\n");

                                                                               }

                                                                            spin_lock(&cdev->lock);

                                                                             value = set_config(cdev, ctrl, w_value);

                                                                             spin_unlock(&cdev->lock);

                                                                             break;

                                   case USB_REQ_GET_CONFIGURATION: //返回当前配置

                                                                          if (ctrl->bRequestType != USB_DIR_IN)

                                                                                                                  goto unknown;

                                                                         if (cdev->config)

                                                                                                                *(u8 *)req->buf = cdev->config->bConfigurationValue;

                                                                         else

                                                                                                                   *(u8 *)req->buf = 0;

                                                                          value = min(w_length, (u16) 1);

                                                                           break;

                            /* function drivers must handle get/set altsetting; if there's

                             * no get() method, we know only altsetting zero works.

                             */

                           case USB_REQ_SET_INTERFACE: //设置接口设置

                                                                         if (ctrl->bRequestType != USB_RECIP_INTERFACE)

                                                                                                                         goto unknown;

                                                                        if (!cdev->config || w_index >= MAX_CONFIG_INTERFACES)

                                                                                                                          break;

                                                                        f = cdev->config->interface[intf];

                                                                        if (!f)

                                                                                     break;

                                                                       if (w_value && !f->set_alt)

                                                                                      break;

                                                                      value = f->set_alt(f, w_index, w_value);

                                                                       break;

                                 case USB_REQ_GET_INTERFACE: //返回接口设置

                                                                   if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE))

                                                                                                    goto unknown;

                                                                  if (!cdev->config || w_index >= MAX_CONFIG_INTERFACES)

                                                                                                    break;

                                                                    f = cdev->config->interface[intf];

                                                                  if (!f)

                                                                                                break;

                                                                  /* lots of interfaces only need altsetting zero... */

                                                                   value = f->get_alt ? f->get_alt(f, w_index) : 0;

                                                                   if (value < 0)

                                                                                                    break;

                                                                  *((u8 *)req->buf) = value;

                                                                  value = min(w_length, (u16) 1);

                                                                   break;

                                default:

   unknown:

              VDBG(cdev, "non-core control req%02x.%02x v%04x i%04x l%d\n",ctrl->bRequestType, ctrl->bRequest,w_value, w_index, w_length);

              /* functions always handle their interfaces and endpoints...

                * punt other recipients (other, WUSB, ...) to the current

                * configuration code.

                *

                * REVISIT it could make sense to let the composite device

                * take such requests too, if that's ever needed: to work

                * in config 0, etc.

              */

          switch (ctrl->bRequestType & USB_RECIP_MASK) {

                       case USB_RECIP_INTERFACE:

                                       f = cdev->config->interface[intf];

                                       break;

                      case USB_RECIP_ENDPOINT:

                                      endp = ((w_index & 0x80) >> 3) | (w_index & 0x0f);

                                       list_for_each_entry(f, &cdev->config->functions, list) {

                                                    if (test_bit(endp, f->endpoints))

                                                                                     break;

                                         }

                                       if (&f->list == &cdev->config->functions)

                                                                     f = NULL;

                                     break;

       }

       if (f && f->setup)

                       value = f->setup(f, ctrl);

     else {

                        struct usb_configuration *c;

                         c = cdev->config;

                         if (c && c->setup)

                                       value = c->setup(c, ctrl);

       }

     goto done;

}

   /* respond with data transfer before status phase? */

    if (value >= 0) {

                           req->length = value;

                              req->zero = value < w_length;

                          value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC);

                        if (value < 0) {

                                        DBG(cdev, "ep_queue --> %d\n", value);

                                         req->status = 0;

                                       composite_setup_complete(gadget->ep0, req);

                      }

         }

done:

           /* device either stalls (value < 0) or reports success */

             return value;

}

USB设备驱动程序-USB Gadget Driver(四)

Gadget 功能层

Gadget功能层完成USB设备的具体功能,其表现的形式各不相同,如键盘、鼠标、存储和网卡等等。功能层不仅涉及到Gadget驱动相关的内容,还涉及到其功能相关的内核子系统。如存储还涉及到内核存储子系统,网卡还涉及到网络驱动子系统。因此,Gadget功能的代码非常复杂。这里以zero.c为例,这个模块只是简单地将接收的数据回显回去。

一、数据结构

首先需要实现usb_composite_driver函数集:

static struct usb_composite_driver zero_driver = {

                    .name = "zero",

                    .dev = &device_desc,

                    .strings = dev_strings,

.bind = zero_bind,

                     .unbind = zero_unbind,

                   .suspend = zero_suspend,

                  .resume = zero_resume,

};

二、主要函数

这个模块的实现就是这么简单:

static int __init init(void)

{

           return usb_composite_register(&zero_driver);

}

module_init(init);

static void __exit cleanup(void)

{

usb_composite_unregister(&zero_driver);

}

Bind函数是功能层需要实现与设备层关联的重要函数:

static int __init zero_bind(struct usb_composite_dev *cdev)

{

           int gcnum;

             struct usb_gadget *gadget = cdev->gadget; //Gadget设备

            int id;

         /* Allocate string descriptor numbers ... note that string

               * contents can be overridden by the composite_dev glue.

                    */

        /*分配字符串描述符的id,并赋值给设备描述符中字符串索引*/

         id = usb_string_id(cdev);

         strings_dev[STRING_MANUFACTURER_IDX].id = id;

         device_desc.iManufacturer = id;

          id = usb_string_id(cdev); i

          strings_dev[STRING_PRODUCT_IDX].id = id;

          device_desc.iProduct = id;

           id = usb_string_id(cdev);

            strings_dev[STRING_SERIAL_IDX].id = id;

            device_desc.iSerialNumber = id;

           /*设置挂起后,设备自动恢复的定时器*/

            setup_timer(&autoresume_timer, zero_autoresume, (unsigned long) cdev);

             /*核心代码,实现功能*/

            if (loopdefault) {

   loopback_add(cdev, autoresume != 0); //数据简单回显功能

                             if (!gadget_is_sh(gadget))

                                                sourcesink_add(cdev, autoresume != 0);

             }

             else

             {

                           sourcesink_add(cdev, autoresume != 0);

                            if (!gadget_is_sh(gadget))

                                                loopback_add(cdev, autoresume != 0);

               }

              /*初始化设备描述符*/

           gcnum = usb_gadget_controller_number(gadget);

          if (gcnum >= 0)

                          device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum);

           else {

                         device_desc.bcdDevice = cpu_to_le16(0x9999);

           }

         return 0;

}

/*增加数据简单回显功能*/

int __init loopback_add(struct usb_composite_dev *cdev, bool autoresume)

{

                         int id;

                         /*获取字符串描述符id索引*/

                         id = usb_string_id(cdev);

                          strings_loopback[0].id = id;

                          loopback_intf.iInterface = id;

                          loopback_driver.iConfiguration = id;

                            /* support autoresume for remote wakeup testing */

                          if (autoresume)

                                                sourcesink_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;

                         /* support OTG systems */

                         if (gadget_is_otg(cdev->gadget)) {

                                                loopback_driver.descriptors = otg_desc;

                                                loopback_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;

                          }

                         return usb_add_config(cdev, &loopback_driver); //增加一个配置

}

/*loopback配置*/

static struct usb_configuration loopback_driver = {

                            .label = "loopback",

                            .strings = loopback_strings,

.bind = loopback_bind_config,

                           .bConfigurationValue = 2,

                          .bmAttributes = USB_CONFIG_ATT_SELFPOWER,

                          /* .iConfiguration = DYNAMIC */

};

将增加配置的usb_add_config函数中会调用其bind函数,即loopback_bind_config函数,来分配这个配置所需要的资源。

struct f_loopback {

                        struct usb_function function;

                        struct usb_ep *in_ep;

                        struct usb_ep *out_ep;

};

static int __init loopback_bind_config(struct usb_configuration *c)

{

                          struct f_loopback *loop;

                           int status;

                           loop = kzalloc(sizeof *loop, GFP_KERNEL); //分配一个loop结构

                          if (!loop)

                                       return -ENOMEM;

                         /*初始化一个功能*/

                           loop->function.name = "loopback";

                             loop->function.descriptors = fs_loopback_descs;

loop->function.bind = loopback_bind;

                           loop->function.unbind = loopback_unbind;

                           loop->function.set_alt = loopback_set_alt;

                           loop->function.disable = loopback_disable;

                        status = usb_add_function(c, &loop->function); //加入这个功能

                        if (status)

                                                kfree(loop);

                         return status;

}

在usb_add_function函数中,又会调用这个功能的bind函数,即loopback_bind函数:

static int __init  loopback_bind(struct usb_configuration *c, struct usb_function *f)

{

                     struct usb_composite_dev *cdev = c->cdev;

                       struct f_loopback *loop = func_to_loop(f);

                     int id;

                     /* allocate interface ID(s) */

                    id = usb_interface_id(c, f); //分配一个接口id

                    if (id < 0)

                                  return id;

                   loopback_intf.bInterfaceNumber = id;

                    /* allocate endpoints */

                     /*返回一个输入端点*/

  loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_source_desc);

                       if (!loop->in_ep) {

                                          autoconf_fail:

                                                     ERROR(cdev, "%s: can't autoconfigure on %s\n", f->name, cdev->gadget->name);

                                                    return -ENODEV;

                     }

                    loop->in_ep->driver_data = cdev; /* claim */

                    /*返回一个输出端点,返回合适的端点*/

  loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc);

                  if (!loop->out_ep)

                                       goto autoconf_fail;

                   loop->out_ep->driver_data = cdev; /* claim */

                    /* support high speed hardware */

                     if (gadget_is_dualspeed(c->cdev->gadget)) {

                                                           hs_loop_source_desc.bEndpointAddress = fs_loop_source_desc.bEndpointAddress;

                                                          hs_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress;

                                                            f->hs_descriptors = hs_loopback_descs;

                          }

                        DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",f->name, loop->in_ep->name, loop->out_ep->name);

          return 0;

}

功能的实现

Loopback_set_alt函数将在设备层的setup函数中被调用,控制通信设置接口。

static int loopback_set_alt(struct usb_function *f, unsigned intf, unsigned alt)

{

                  struct f_loopback *loop = func_to_loop(f);

                   struct usb_composite_dev *cdev = f->config->cdev;

                       /* we know alt is zero */

                   if (loop->in_ep->driver_data)

                                           disable_loopback(loop);

                   return enable_loopback(cdev, loop); //开启功能

}

static int enable_loopback(struct usb_composite_dev *cdev, struct f_loopback *loop)

{

                     int result = 0;

                     const struct usb_endpoint_descriptor *src, *sink;

                        struct usb_ep *ep;

                       struct usb_request *req;

                        unsigned i;

                     /*选择端点描述符*/

          src = ep_choose(cdev->gadget, &hs_loop_source_desc, &fs_loop_source_desc);

                  sink = ep_choose(cdev->gadget, &hs_loop_sink_desc, &fs_loop_sink_desc);

                     /* one endpoint writes data back IN to the host */

                    /*输入输出端点使能*/

                      ep = loop->in_ep;

result = usb_ep_enable(ep, src);

                      if (result < 0)

                                           return result;

                      ep->driver_data = loop;

                      /* one endpoint just reads OUT packets */

                       ep = loop->out_ep;

result = usb_ep_enable(ep, sink);

                       if (result < 0) {

                                              fail0:

                                                  ep = loop->in_ep;

                                                   usb_ep_disable(ep);

                                                  ep->driver_data = NULL;

                                return result;

                       }

                      ep->driver_data = loop;

                     /* allocate a bunch of read buffers and queue them all at once.

                         * we buffer at most 'qlen' transfers; fewer if any need more

                         * than 'buflen' bytes each.

                        */

                      /*qlen=32,分配32个请求,将这个请求放入输出端点队列,等待接收数据*/

                        for (i = 0; i < qlen && result == 0; i++) {

    req = alloc_ep_req(ep);

                                                    if (req) {

req->complete = loopback_complete;

                                                                  result = usb_ep_queue(ep, req, GFP_ATOMIC);

                                                                  if (result)

                                                                                ERROR(cdev, "%s queue req --> %d\n", ep->name, result);

                                                    }

                                                    else {

                                                                     usb_ep_disable(ep);

                                                                    ep->driver_data = NULL;

                                                                    result = -ENOMEM;

                                                                  goto fail0;

                                               }

                            }

                       DBG(cdev, "%s enabled\n", loop->function.name);

                      return result;

}

/*接收到数据之后,将调用这个完成函数*/

static void loopback_complete(struct usb_ep *ep, struct usb_request *req)

{

                  struct f_loopback *loop = ep->driver_data;

                 struct usb_composite_dev *cdev = loop->function.config->cdev;

                   int status = req->status;

                  switch (status) {

                                 case 0: /* normal completion? */

                                            if (ep == loop->out_ep) { //将接收到的数据放入输入端点,返回给主机

                                                                                  /* loop this OUT packet back IN to the host */

                                                                                 req->zero = (req->actual < req->length);

                                                                                 req->length = req->actual;

    status = usb_ep_queue(loop->in_ep, req, GFP_ATOMIC);

                                                                                if (status == 0)

                                                                                                      return;

                                                                                 /* "should never get here" */

                                                                                ERROR(cdev, "can't loop %s to %s: %d\n", ep->name, loop->in_ep->name,status);

                                            }

                                     /* queue the buffer for some later OUT packet */

                                   req->length = buflen; //将输入端点完成的申请,重新放入输出队列,等待接收新的数据

   status = usb_ep_queue(loop->out_ep, req, GFP_ATOMIC);

                                    if (status == 0)

                                                          return;

                                   /* "should never get here" */

                                    /* FALLTHROUGH */

                     default:

                                      ERROR(cdev, "%s loop complete --> %d, %d/%d\n", ep->name, status, req->actual, req->length);

                                       /* FALLTHROUGH */

                                       /* NOTE: since this driver doesn't maintain an explicit record

                                               * of requests it submitted (just maintains qlen count), we

                                           * rely on the hardware driver to clean up on disconnect or

                                            * endpoint disable.

                                           */

                case -ECONNABORTED: /* hardware forced ep reset */

                  case -ECONNRESET: /* request dequeued */

                 case -ESHUTDOWN: /* disconnect from host */

free_ep_req(ep, req);

                                          return;

                    }

}

标签:gadget,struct,Gadget,Driver,udc,cdev,usb,ep,USB
From: https://www.cnblogs.com/kn-zheng/p/17074897.html

相关文章

  • Linux下编写USB驱动实例
    USB是连接计算机系统与外部设备的一种串口总线标准,也是一种输入输出接口的技术规范,被广泛地应用于个人电脑和移动设备等信息通讯产品,USB就是简写,中文叫通用串行总线。我......
  • 【模板】ODT | Old Driver Tree | 珂朵莉树
    postedon2021-04-0220:38:49|under学术|source这个东西本身不常用,但是这个维护连续段的写法很常用。标签:暴力、数据结构、std::set#include<set>template<cla......
  • USB摄像头驱动实现源码分析
    Spac5xx的实现是按照标准的USBVIDEO设备的驱动框架编写(其具体的驱动框架可参照/usr/src/linux/drivers/usb/usbvideo.c文件),整个源程序由四个主体部分组成:总结送免费学习......
  • USB驱动开发学习(键盘+鼠标)
    昨天学习了一下usb鼠标的简单识别,今天来完整的写一套键盘和鼠标的驱动,起码能够支持树莓派使用的。先来写一下键盘的驱动。键盘驱动框架框架部分很简单,和昨天的鼠标基本一......
  • usb驱动开发19——驱动生命线
    现在开始就沿着usb_generic_driver的生命线继续往下走。设备的生命线你可以为是从你的usb设备连接到hub的某个端口时开始,而驱动的生命线就必须得回溯到usb子系统的初始化函......
  • 华为C8650USB连接win10无法访问内部储存空间
    C8650,MIUI-破落MIUI-2.3.7无tf卡时显示内部储存150MB不可用,显示SD卡18MB可卸载猜测是分区问题计算机管理-设备-AndroidAdapter显示此设备配置不正确(code1)磁盘空间不足......
  • WIN版虚拟显示器usbmmidd_v2
    未连接显示器时在Windows10上激活辅助显示器在没有物理显示器的情况下,通过远程软件向日葵或者todesk连接主机,默认显示640*640分辨率,而且无法修改,网上存在一些付费版虚......
  • 《安富莱嵌入式周报》第300期:几百种炫酷灯阵玩法, USB Web网页固件升级,波士顿动力整
    往期周报汇总地址:http://www.armbbs.cn/forum.php?mod=forumdisplay&fid=12&filter=typeid&typeid=104 祝大家春节快乐视频版:https://www.bilibili.com/video/BV1U......
  • DBeaver 连接IoTDBDriver
    DBeaver是一个SQL客户端和数据库管理工具。DBeaver可以使用IoTDB的JDBC驱动与IoTDB进行交互。IoTDB官网是有说明的,官网的方法是从源码编译​​https://iotdb.ap......
  • 解决Loading class `com.mysql.jdbc.Driver'. This is deprecated.
    IDEA编写的数据库报红字:Loadingclass`com.mysql.jdbc.Driver'.Thisisdeprecated.Thenewdriverclassis`com.mysql.cj.jdbc.Driver'.Thedriverisautomatical......