绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
LINUX下USB1.1设备学习小记(4)_uhci(7)
2020-06-03 15:15:12
uhci_hub_control在/drivers/usb/host/uhci-hcd.c

static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
   u16 wIndex, char *buf, u16 wLength)
{
 struct uhci_hcd *uhci = hcd_to_uhci(hcd);
 int status, lstatus, retval = 0, len = 0;
 unsigned int port = wIndex - 1;
 unsigned long port_addr = uhci->io_addr + USBPORTSC1 + 2 * port;
 u16 wPortChange, wPortStatus;
 unsigned long flags;
 if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) || uhci->dead)
  return -ETIMEDOUT;
 spin_lock_irqsave(&uhci->lock, flags);
 switch (typeReq)
 {
 
//获取集线器状态
 case GetHubStatus:
  *(__le32 *)buf = cpu_to_le32();
  OK(4); /* hub power */
 
//获取端口状态
 case GetPortStatus:
  
//要求的端口号大于实际集线器有的端口数
  if (port >= uhci->rh_numports)
   goto err;
  uhci_check_ports(uhci);
  
//获得对应的端口地址的寄存器数据
  status = inw(port_addr);
  
/* Intel controllers report the OverCurrent bit active on.
   * VIA controllers report it active off, so we'll adjust the
   * bit value. (It's not standardized in the UHCI spec.)
   */

   
//检测是否为VIA的产品
  if (to_pci_dev(hcd->self.controller)->vendor == PCI_VENDOR_ID_VIA)
   status ^= USBPORTSC_OC;
  /* UHCI doesn't support C_RESET (always false) */
  wPortChange = lstatus = 0;
  
//检测连接状态改变位
  if (status & USBPORTSC_CSC)
   wPortChange |= USB_PORT_STAT_C_CONNECTION;
  
//检测端口有效状态改变位
  if (status & USBPORTSC_PEC)
   wPortChange |= USB_PORT_STAT_C_ENABLE;
  
//检测过电保护改变位
  if ((status & USBPORTSC_OCC) && !ignore_oc)
   wPortChange |= USB_PORT_STAT_C_OVERCURRENT;
  
//检测端口悬挂位
  if (test_bit(port, &uhci->port_c_suspend)) 
  {
   wPortChange |= USB_PORT_STAT_C_SUSPEND;
   lstatus |= 1;
  }
  
//检测端口恢复位
  if (test_bit(port, &uhci->resuming_ports))
   lstatus |= 4;
  /* UHCI has no power switching (always on) */
  
//设置端口供电状态为正常
  wPortStatus = USB_PORT_STAT_POWER;
  
//检测当前连接状态位
  if (status & USBPORTSC_CCS)
   wPortStatus |= USB_PORT_STAT_CONNECTION;
  
//检测端口有效位
  if (status & USBPORTSC_PE) 
  {
   wPortStatus |= USB_PORT_STAT_ENABLE;
   
//检测端口挂起/恢复位
   if (status & SUSPEND_BITS)
    wPortStatus |= USB_PORT_STAT_SUSPEND;
  }
  
//检测电源超载位
  if (status & USBPORTSC_OC)
   wPortStatus |= USB_PORT_STAT_OVERCURRENT;
  
//检测端口复位位
  if (status & USBPORTSC_PR)
   wPortStatus |= USB_PORT_STAT_RESET;
  
//检测端口连接的设备是否为低速设备
  if (status & USBPORTSC_LSDA)
   wPortStatus |= USB_PORT_STAT_LOW_SPEED;
  if (wPortChange)
   dev_dbg(uhci_dev(uhci), "port %d portsc %04x,%02x\n",wIndex, status,lstatus);
  
//将状态信息复制到buf的前2字节
  *(__le16 *)buf = cpu_to_le16(wPortStatus);
  
//将状态改变信息复制到buf的后2字节
  *(__le16 *)(buf + 2) = cpu_to_le16(wPortChange);
  OK(4);
 case SetHubFeature: /* We don't implement these */
 case ClearHubFeature:
  switch (wValue) 
  {
  case C_HUB_OVER_CURRENT:
  case C_HUB_LOCAL_POWER:
   OK();
  default:
   goto err;
  }
  break;
 case SetPortFeature:
  if (port >= uhci->rh_numports)
   goto err;
  switch (wValue)
  {
  case USB_PORT_FEAT_SUSPEND:
   SET_RH_PORTSTAT(USBPORTSC_SUSP);
   OK();
  case USB_PORT_FEAT_RESET:
   
//端口发送复位信号
   SET_RH_PORTSTAT(USBPORTSC_PR);
   /* Reset terminates Resume signalling */
   
//停止发送恢复信号
   uhci_finish_suspend(uhci, port, port_addr);
   /* USB v2.0 7.1.7.5 */
   uhci->ports_timeout = jiffies + msecs_to_jiffies(50);
   OK();
  case USB_PORT_FEAT_POWER:
   /* UHCI has no power switching */
   OK();
  default:
   goto err;
  }
  break;
 case ClearPortFeature:
  
//检测端口号是否大于集线器的端口数
  if (port >= uhci->rh_numports)
   goto err;
  switch (wValue) 
  {
  case USB_PORT_FEAT_ENABLE:
   CLR_RH_PORTSTAT(USBPORTSC_PE);
   /* Disable terminates Resume signalling */
   uhci_finish_suspend(uhci, port, port_addr);
   OK();
  case USB_PORT_FEAT_C_ENABLE:
   CLR_RH_PORTSTAT(USBPORTSC_PEC);
   OK();
  case USB_PORT_FEAT_SUSPEND:
   if (!(inw(port_addr) & USBPORTSC_SUSP)) 
   {
    /* Make certain the port isn't suspended */
    uhci_finish_suspend(uhci, port, port_addr);
   } 
   else if (!test_and_set_bit(port,&uhci->resuming_ports)) 
   {
    SET_RH_PORTSTAT(USBPORTSC_RD);
    
/* The controller won't allow RD to be set
     * if the port is disabled. When this happens
     * just skip the Resume signalling.
     */

    if (!(inw(port_addr) & USBPORTSC_RD))
     uhci_finish_suspend(uhci, port,
       port_addr);
    else
     /* USB v2.0 7.1.7.7 */
     uhci->ports_timeout = jiffies +
      msecs_to_jiffies(20);
   }
   OK();
  case USB_PORT_FEAT_C_SUSPEND:
   clear_bit(port, &uhci->port_c_suspend);
   OK();
  case USB_PORT_FEAT_POWER:
   /* UHCI has no power switching */
   goto err;
  case USB_PORT_FEAT_C_CONNECTION:
   CLR_RH_PORTSTAT(USBPORTSC_CSC);
   OK();
  case USB_PORT_FEAT_C_OVER_CURRENT:
   CLR_RH_PORTSTAT(USBPORTSC_OCC);
   OK();
  case USB_PORT_FEAT_C_RESET:
   /* this driver won't report these */
   OK();
  default:
   goto err;
  }
  break;
 case GetHubDescriptor:
  len = min_t(unsigned int, sizeof(root_hub_hub_des), wLength);
  memcpy(buf, root_hub_hub_des, len);
  if (len > 2)
   buf[2] = uhci->rh_numports;
  OK(len);
 default:
err:
  retval = -EPIPE;
 }
 spin_unlock_irqrestore(&uhci->lock, flags);
 return retval;
}

这里又是一个大的switch,这次我们的case是GetHubDescriptor,得到集线器描述符root_hub_hub_des,它的结构如下
static __u8 root_hub_hub_des[] =
{
 0x09,   /*  __u8  bLength; */
 0x29,   /*  __u8  bDescriptorType; Hub-descriptor */
 0x02,   /*  __u8  bNbrPorts; */
 0x0a,   /* __u16  wHubCharacteristics; */
 0x00,   /*   (per-port OC, no power switching) */
 0x01,   /*  __u8  bPwrOn2pwrGood; 2ms */
 0x00,   /*  __u8  bHubContrCurrent; 0 mA */
 0x00,   /*  __u8  DeviceRemovable; *** 7 Ports max *** */
 0xff   /*  __u8  PortPwrCtrlMask; *** 7 ports max *** */
};
好~ 获取集线器描述符的任务就完成了,现在到usb_get_status
 

usb_get_status在/drivers/usb/core/messgae.c

int usb_get_status(struct usb_device *dev, int type, int target, void *data)
{
 int ret;
 u16 *status = kmalloc(sizeof(*status), GFP_KERNEL);
 if (!status)
  return -ENOMEM;
 ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
  USB_REQ_GET_STATUS, USB_DIR_IN | type, 0, target, status,
  sizeof(*status), USB_CTRL_GET_TIMEOUT);
 *(u16 *)data = *status;
 kfree(status);
 return ret;
}

又是usb_control_msg,我们又来到了rh_call_control,这次的case为DeviceRequest | USB_REQ_GET_STATUS,去了一些关于电源的参数(我也不清楚这些电源参数是干什么的,就不分析了)
hub_hub_status取得集线器的状态
 

hub_hub_status在/drivers/usb/core/hub.c
 

static int hub_hub_status(struct usb_hub *hub,
  u16 *status, u16 *change)
{
 int ret;
 mutex_lock(&hub->status_mutex);
 
//取得集线器状态
 ret = get_hub_status(hub->hdev, &hub->status->hub);
 if (ret < 0)
  dev_err (hub->intfdev,"%s failed (err = %d)\n", __func__, ret);
 else 
 {
  *status = le16_to_cpu(hub->status->hub.wHubStatus);
  *change = le16_to_cpu(hub->status->hub.wHubChange);
  ret = 0;
 }
 mutex_unlock(&hub->status_mutex);
 return ret;
}

get_hub_status在/drivers/usb/core/hub.c

static int get_hub_status(struct usb_device *hdev,
  struct usb_hub_status *data)
{
 int i, status = -ETIMEDOUT;
 for (= 0; i < USB_STS_RETRIES && status == -ETIMEDOUT; i++) 
 {
  status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
   USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_HUB, 0, 0,
   data, sizeof(*data), USB_STS_TIMEOUT);
 }
 return status;
}

 
usb_control_msg又见面了,穿过rh_call_control,再临uhci_hub_control,这次的case为GetHubStatus, *(__le32 *)buf = cpu_to_le32(0);  赏了全为0的数据.....  白跑一趟啊
pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress);
#define usb_rcvintpipe(dev,endpoint) \
 ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
usb_rcvintpipe是一个宏,它的作用是生成一个断管道,注意看,是
然后把得到的管道赋到了urb上,还有就是这里的complete为hub_irq
接着到hub_power_on
 

hub_power_on在/drivers/usb/core/hub.c

static void hub_power_on(struct usb_hub *hub)
{
 int port1;
 unsigned pgood_delay = hub->descriptor->bPwrOn2PwrGood * 2;
 u16 wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
 
/* Enable power on each port. Some hubs have reserved values
  * of LPSM (> 2) in their descriptors, even though they are
  * USB 2.0 hubs. Some hubs do not implement port-power switching
  * but only emulate it. In all cases, the ports won't work
  * unless we send these messages to the hub.
  */

 if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2)
  dev_dbg(hub->intfdev, "enabling power on all ports\n");
 else
  dev_dbg(hub->intfdev, "trying to enable port power on " "non-switchable hub\n");
 
//历遍端口
 for (port1 = 1; port1 <= hub->descriptor->bNbrPorts; port1++)
  
//设置端口的灵巧电源分配
  set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER);
 /* Wait at least 100 msec for power to become stable */
 msleep(max(pgood_delay, (unsigned) 100));
}

 
set_port_feature在/drivers/usb/core/hub.c

static int set_port_feature(struct usb_device *hdev, int port1, int feature)
{
 return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
  USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port1,
  NULL, 0, 1000);
}

usb_control_msg.......... 自觉来到rh_call_control并跳到uhci_hub_control吧,这次的case为SetPortFeature,什么特性呢? USB_PORT_FEAT_POWER特性
/* UHCI has no power switching */
噢~ 太好了,啥也不用做,返回~
到hub_activate
 

hub_activate在/drivers/usb/core/hub.c

static void hub_activate(struct usb_hub *hub)
{
 int status;
 hub->quiescing = 0;
 hub->activating = 1;
 
//发送中断urb
 status = usb_submit_urb(hub->urb, GFP_NOIO);
 
 if (status < 0)
  dev_err(hub->intfdev, "activate --> %d\n", status);

 if (hub->has_indicators && blinkenlights)
  schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD);
 /* scan all ports ASAP */
 kick_khubd(hub);
}

发送断urb,来到rh_queue_status,咦? rh_queue_status在哪?看rh_urb_enqueue,在rh_call_control的上面,为什么到rh_queue_status了呢?还记得前面提到的管道么,这次给urb的是断类型的管道,所以到了rh_queue_status
 
 
rh_queue_status在/drivers/usb/core/hcd.c

static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb)
{
 int retval;
 unsigned long flags;
 int len = 1 + (urb->dev->maxchild / 8);
 spin_lock_irqsave (&hcd_root_hub_lock, flags);
 if (hcd->status_urb || urb->transfer_buffer_length < len) 
 {
  dev_dbg (hcd->self.controller, "not queuing rh status urb\n");
  retval = -EINVAL;
  goto done;
 }
 
//连接urb到端点
 retval = usb_hcd_link_urb_to_ep(hcd, urb);
 if (retval)
  goto done;
 
//连接urb到主机控制器驱动上
 hcd->status_urb = urb;
 
//连接主机控制器驱动到urb的私有数据上
 urb->hcpriv = hcd; /* indicate it's queued */
 if (!hcd->uses_new_polling)
  mod_timer(&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
 /* If a status change has already occurred, report it ASAP */
 else if (hcd->poll_pending)
  mod_timer(&hcd->rh_timer, jiffies);
 retval = 0;
 done:
 spin_unlock_irqrestore (&hcd_root_hub_lock, flags);
 return retval;
}

执行完之后到了kick_khubd,关于kick_khubd现在我保密,在之后的文解谜 = 3=
现在hub_driver的probe终于执行完了........
usb_generic_driver的probe也终于执行完了..............
usb_new_device也终于执行完了
register_root_hub也终于执行完了
跳回到usb_add_hcd,现在只剩下后一个函数usb_hcd_poll_rh_status了
usb_hcd_poll_rh_status是主机控制器驱动重要的函数之一,这个函数负责轮询根集线器的端口,有设备了嘛? 有设备了嘛? 有设备了嘛?........... 直到关闭系统~
现在就来看看usb_hcd_poll_rh_status
 
 
usb_hcd_poll_rh_status在/drivers/usb/core/hcd.c

void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
{
 struct urb *urb;
 int length;
 unsigned long flags;
 char buffer[4]; /* Any root hubs with > 31 ports? */
 
//检测主机控制器驱动是否已经注册
 if (unlikely(!hcd->rh_registered))
  return;
 if (!hcd->uses_new_polling && !hcd->status_urb)
  return;
 
//进行集线器设备状态检测
 length = hcd->driver->hub_status_data(hcd, buffer);
 
//端口有设备
 if (length > 0)
 {
  /* try to complete the status urb */
  spin_lock_irqsave(&hcd_root_hub_lock, flags);
  urb = hcd->status_urb;
  
//检测urb是否存在
  if (urb) 
  {
   hcd->poll_pending = 0;
   
//清除hcd的状态urb
   hcd->status_urb = NULL;
   
//置实际传输长度为1
   urb->actual_length = length;
   
//拷贝端口状态描述组到urb中
   memcpy(urb->transfer_buffer, buffer, length);
   
//卸载urb与节点的连接
   usb_hcd_unlink_urb_from_ep(hcd, urb);
   spin_unlock(&hcd_root_hub_lock);
   
//返回urb给驱动程序
   usb_hcd_giveback_urb(hcd, urb, 0);
   spin_lock(&hcd_root_hub_lock);
  } 
  else 
  {
   length = 0;
   hcd->poll_pending = 1;
  }
  spin_unlock_irqrestore(&hcd_root_hub_lock, flags);
 }
 
/* The USB 2.0 spec says 256 ms. This is close enough and won't
  * exceed that limit if HZ is 100. The math is more clunky than
  * maybe expected, this is to make sure that all timers for USB devices
  * fire at the same time to give the CPU a break inbetween */

 
//每间隔HZ/4就执行一次hcd->rh_timer中指定的函数
 if (hcd->uses_new_polling ? hcd->poll_rh :(length == 0 && hcd->status_urb !=NULL))
  mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
}

hcd->driver->hub_status_data检测集线器的端口状态,uhci对应的函数为uhci_hub_status_data
 
uhci_hub_status_data在/drivers/usb/host/uhci-hub.c

static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)
{
 
//取得uhci_hcd结构
 struct uhci_hcd *uhci = hcd_to_uhci(hcd);
 unsigned long flags;
 int status = 0;
 spin_lock_irqsave(&uhci->lock, flags);
 
//调度uhci中的帧队列
 uhci_scan_schedule(uhci);
 if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) || uhci->dead)
  goto done;
 
//检测端口
 uhci_check_ports(uhci);
 
//获得端口状态
 status = get_hub_status_data(uhci, buf);
 
//检测根集线器的状态
 switch (uhci->rh_state) 
 {
     case UHCI_RH_SUSPENDING:
     case UHCI_RH_SUSPENDED:
  /* if port change, ask to be resumed */
  if (status)
   
//唤醒根集线器
   usb_hcd_resume_root_hub(hcd);
  break;
     case UHCI_RH_AUTO_STOPPED:
  /* if port change, auto start */
  if (status)
   
//唤醒主机控制器
   wakeup_rh(uhci);
  break;
     
//uhci的状态为运行
     case UHCI_RH_RUNNING:
  /* are any devices attached? */
  
//检测是否有连接的设备
  if (!any_ports_active(uhci)) 
  {
   
//改变uhci的状态为运行但无设备
   uhci->rh_state = UHCI_RH_RUNNING_NODEVS;
   
//改变uhci的自动停止时间
   uhci->auto_stop_time = jiffies + HZ;
  }
  break;
     
//uhci的状态为运行但无设备
     case UHCI_RH_RUNNING_NODEVS:
  /* auto-stop if nothing connected for 1 second */
  
//检测是否有连接的设备
  if (any_ports_active(uhci))
   
//改变uhci的状态为运行
   uhci->rh_state = UHCI_RH_RUNNING;
  
//检测jiffies是否大于uhci->auto_stop_time
  else if (time_after_eq(jiffies, uhci->auto_stop_time))
   
//悬挂主机控制器
   suspend_rh(uhci, UHCI_RH_AUTO_STOPPED);
  break;
     default:
  break;
 }
done:
 spin_unlock_irqrestore(&uhci->lock, flags);
 return status;
}

uhci_scan_schedule为扫描帧队列的任务,这个函数的内容先保密 = 3=
uhci_check_ports为检测端口的复位状态
 

uhci_check_ports在/drivers/usb/host/uhci-hub.c

static void uhci_check_ports(struct uhci_hcd *uhci)
{
 unsigned int port;
 unsigned long port_addr;
 int status;
 
//历遍根集线器的所有端口
 for (port = 0; port < uhci->rh_numports; ++port) 
 {
  
//获取端口的地址
  port_addr = uhci->io_addr + USBPORTSC1 + 2 * port;
  
//读取端口状态
  status = inw(port_addr);
  
//判断端口是否为重置状态
  if (unlikely(status & USBPORTSC_PR)) 
  {
   
//判断jiffies是否大于uhci->ports_timeout
   
//也就是判断端口的复位信号是否足够长了
   if (time_after_eq(jiffies, uhci->ports_timeout)) 
   {
    
//清除端口的复位状态,停止发送复位信号
    CLR_RH_PORTSTAT(USBPORTSC_PR);
    
//延迟10微妙
    udelay(10);
    
/* HP's server management chip requires
     * a longer delay. */

     
//测试是否为惠普产品,惠普产品需要更长的延迟
    if (to_pci_dev(uhci_dev(uhci))->vendor == PCI_VENDOR_ID_HP)
     wait_for_HP(port_addr);
    
/* If the port was enabled before, turning
     * reset on caused a port enable change.
     * Turning reset off causes a port connect
     * status change. Clear these changes. */

     
//清除连接状态改变位和端点使能改变位
    CLR_RH_PORTSTAT(USBPORTSC_CSC | USBPORTSC_PEC);
    
//设置端点使能位
    SET_RH_PORTSTAT(USBPORTSC_PE);
   }
  }
  
//判断端口是否为探测恢复状态
  if (unlikely(status & USBPORTSC_RD)) 
  {
   
//检测uhci的恢复位上对应端口的状态
   if (!test_bit(port, &uhci->resuming_ports))
   {
    /* Port received a wakeup request */
    
//设置对应端口的恢复位
    set_bit(port, &uhci->resuming_ports);
    
//为恢复延迟设定再检测时间
    uhci->ports_timeout = jiffies + msecs_to_jiffies(20);
    
/* Make sure we see the port again
     * after the resuming period is over. */

    mod_timer(&uhci_to_hcd(uhci)->rh_timer,uhci->ports_timeout);
   } 
   else if (time_after_eq(jiffies,uhci->ports_timeout)) 
   {
    uhci_finish_suspend(uhci, port, port_addr);
   }
  }
 }
}

get_hub_status_data负责检测端口的连接状态
 

get_hub_status_data在/drivers/usb/host/uhci-hub.c

static inline int get_hub_status_data(struct uhci_hcd *uhci, char *buf)
{
 int port;
 
//检测RWC的三个状态改变位
 int mask = RWC_BITS;
 
/* Some boards (both VIA and Intel apparently) report bogus
  * overcurrent indications, causing massive log spam unless
  * we completely ignore them. This doesn't seem to be a problem
  * with the chipset so much as with the way it is connected on
  * the motherboard; if the overcurrent input is left to float
  * then it may constantly register false positives. */

 if (ignore_oc)
  mask &= ~USBPORTSC_OCC;
 *buf = 0;
 
//历遍根集线器上的端口
 for (port = 0; port < uhci->rh_numports; ++port) 
 {
  
//检测端口的状态改变位,或者端口悬挂位
  if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) & mask) || test_bit(port,&uhci->port_c_suspend))
   
//记录端口号
   *buf |= (<< (port + 1));
 } 
 return !!*buf;
}

 
后的返回值有两个!!,是转换数值为0或者1,例如!(!10)=!(0)=1,!(!0)=!(1)=0,使得返回值只有0或者1
返回到usb_hcd_poll_rh_status ,后面的代码先保密 = 3= 
后这句mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4))是按HZ/4的时间间隔执行hcd->rh_timer指定的函数,在uhci为rh_timer_func,而rh_timer_func很简单
 
 
rh_timer_func在/drivers/usb/core/hcd.c

static void rh_timer_func (unsigned long _hcd)
{
 usb_hcd_poll_rh_status((struct usb_hcd *) _hcd);
}


只是简单的调用了usb_hcd_poll_rh_status而已~, usb_hcd_poll_rh_status是什么?
前面.......前面....... 刚分析完啊 Orz
这样,每间隔HZ/4就会执行usb_hcd_poll_rh_status,形成轮询
这个rh_timer是在usb_create_hcd注册的,大家可以回头看看 = 3= 
uhci的注册就算完成了,真是漫长啊 T^T  我眼泪都快流不出来了
注册好的主机数据结构图如下

分享好友

分享这个小栈给你的朋友们,一起进步吧。

内核源码
创建时间:2020-05-18 13:36:55
内核源码精华帖内容汇总
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 飘絮絮絮丶
    专家
戳我,来吐槽~