linux usb usbip駆動詳細(五)


引き続きvhci-hcd駆動について説明します.
前の記事ではvhci-hcdの初期化の流れについて説明し、usbip attach-r-bの駆動について説明しました.
私たちはvhciを知っています.start()関数の最後にsysfsのユーザーインタフェースを登録し、vhci-hcdドライバを構成します.drivers/usb/usbip/vhci_を直接読みます.sysfs.cのattachの操作:
/* Sysfs entry to establish a virtual connection */
/*
 * To start a new USB/IP attachment, a userland program needs to setup a TCP
 * connection and then write its socket descriptor with remote device
 * information into this sysfs file.
 *
 * A remote device is virtually attached to the root-hub port of @rhport with
 * @speed. @devid is embedded into a request to specify the remote device in a
 * server host.
 *
 * write() returns 0 on success, else negative errno.
 */
static ssize_t attach_store(struct device *dev, struct device_attribute *attr,
			    const char *buf, size_t count)
{
	struct socket *socket;
	int sockfd = 0;
	__u32 port = 0, pdev_nr = 0, rhport = 0, devid = 0, speed = 0;
	struct usb_hcd *hcd;
	struct vhci_hcd *vhci_hcd;
	struct vhci_device *vdev;
	struct vhci *vhci;

	/*
	 * @rhport: port number of vhci_hcd
	 * @sockfd: socket descriptor of an established TCP connection
	 * @devid: unique device identifier in a remote host
	 * @speed: usb device speed in a remote host
	 */
	if (sscanf(buf, "%u %u %u %u", &port, &sockfd, &devid, &speed) != 4)
		return -EINVAL;
  ...
	rhport = port_to_rhport(port);
  ...

	hcd = platform_get_drvdata(vhcis->pdev);
  ...
  
	vhci_hcd = hcd_to_vhci_hcd(hcd);
	vhci = vhci_hcd->vhci;

	vdev = &vhci->vhci_hcd_hs->vdev[rhport];

	/* Extract socket from fd. */
	socket = sockfd_lookup(sockfd, &err);
  ..
  
	/* now need lock until setting vdev status as used */

	vdev->devid         = devid;
	vdev->speed         = speed;
	vdev->ud.sockfd     = sockfd;
	vdev->ud.tcp_socket = socket;
	vdev->ud.status     = VDEV_ST_NOTASSIGNED;
  ...
  
	vdev->ud.tcp_rx = kthread_get_run(vhci_rx_loop, &vdev->ud, "vhci_rx");
	vdev->ud.tcp_tx = kthread_get_run(vhci_tx_loop, &vdev->ud, "vhci_tx");

	rh_port_connect(vdev, speed);

	return count;
}

説明を容易にするために、コード内のエラー処理、スピンロック初期化、usb 3を削除する.0部およびデフォルトではhcdコントローラが1つしかありません.上位層(ユーザ状態)からbuf文字列データが得られ、sscanfフォーマットを用いて仮想root hubのポート(usb hubには複数のUSBポートがある)、usb速度、socket記述子(ハンドル)、およびデバイスid(devid)が初期化されることがわかる.
static inline __u32 port_to_rhport(__u32 port)
{
	return port % VHCI_HC_PORTS;
}

vhci-hcdはVHCI_を最大サポートするように設計されているためHC_PORTS(8)ポート.
      socket = sockfd_lookup(sockfd, &err);ユーザ状態のsocketハンドルをカーネルに適用されるsocket記述子に変換し、カーネル呼び出しkernel_recvmsgとkernel_sendmsgの場合に使用します.前述したように、tcpの接続確立はアプリケーション層で行われているため、下位駆動はすでに開いているsocketリンクを使用するだけである.次にkthread_を使用しますget_run()は2つのカーネルスレッドvhciを作成しました.rx_loopとvhci_tx_loop、shellでtopコマンドを使用すると、プロセス名が「vhci_rx」および「vhci_tx」であることがわかります.この2つのカーネルスレッドは重要で、基本的にUSBIP_CMD_SUBMIT、USBIP_RET_SUBMIT、USBIP_CMD_UNLINKとUSBIP_RET_UNLINKコマンドは両方のスレッドで処理されます.この2つのスレッドは、以下で特別に分析されます.
最後にrh_を呼び出すport_connect(vdev, speed);この関数の機能は非常に重要で、前述の「vhci-hcd仮想化されたホストコントローラのroot hubを蹴って、hub.cに本当のusbデバイスが挿入されていると思わせる」ことです.
void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed)
{
	struct vhci_hcd	*vhci_hcd = vdev_to_vhci_hcd(vdev);
	struct vhci *vhci = vhci_hcd->vhci;
	int		rhport = vdev->rhport;
	u32		status;
	unsigned long	flags;

	usbip_dbg_vhci_rh("rh_port_connect %d
", rhport); spin_lock_irqsave(&vhci->lock, flags); status = vhci_hcd->port_status[rhport]; status |= USB_PORT_STAT_CONNECTION | (1 << USB_PORT_FEAT_C_CONNECTION); switch (speed) { case USB_SPEED_HIGH: status |= USB_PORT_STAT_HIGH_SPEED; break; case USB_SPEED_LOW: status |= USB_PORT_STAT_LOW_SPEED; break; default: break; } vhci_hcd->port_status[rhport] = status; spin_unlock_irqrestore(&vhci->lock, flags); usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci_hcd)); }

この関数は主に状態status|=USB_を設定します.PORT_STAT_CONNECTION | (1 << USB_PORT_FEAT_C_CONNECTION) | USB_PORT_STAT_HIGH_SPEED、最後にusbを呼び出すhcd_poll_rh_status(vhci_hcd_to_hcd(vhci_hcd));を選択して設定できます.usb_hcd_poll_rh_statusはusb coreのインタフェースで、私たちは分析しないで、作用はさっき言った“hcdのroot bubを蹴って”で、この時struct hc_をコールバックしますdriver vhci_hc_driverのhub_status_data、vhci-hcdインスタンスに対してvhci_hub_status():
/*
 * Returns 0 if the status hasn't changed, or the number of bytes in buf.
 * Ports are 0-indexed from the HCD point of view,
 * and 1-indexed from the USB core pointer of view.
 *
 * @buf: a bitmap to show which port status has been changed.
 *  bit  0: reserved
 *  bit  1: the status of port 0 has been changed.
 *  bit  2: the status of port 1 has been changed.
 *  ...
 */
static int vhci_hub_status(struct usb_hcd *hcd, char *buf)
{
	struct vhci_hcd	*vhci_hcd = hcd_to_vhci_hcd(hcd);
	struct vhci *vhci = vhci_hcd->vhci;
	int		retval = DIV_ROUND_UP(VHCI_HC_PORTS + 1, 8);
	int		rhport;
	int		changed = 0;
	unsigned long	flags;

	memset(buf, 0, retval);

	spin_lock_irqsave(&vhci->lock, flags);
	if (!HCD_HW_ACCESSIBLE(hcd)) {
		usbip_dbg_vhci_rh("hw accessible flag not on?
"); goto done; } /* check pseudo status register for each port */ for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { if ((vhci_hcd->port_status[rhport] & PORT_C_MASK)) { /* The status of a port has been changed, */ usbip_dbg_vhci_rh("port %d status changed
", rhport); buf[(rhport + 1) / 8] |= 1 << (rhport + 1) % 8; changed = 1; } } if ((hcd->state == HC_STATE_SUSPENDED) && (changed == 1)) usb_hcd_resume_root_hub(hcd); done: spin_unlock_irqrestore(&vhci->lock, flags); return changed ? retval : 0; }

virtual root hubのポート番号を「hcdフレームワーク」に報告すると、hcdフレームワークはstruct hc_をコールバックします.driver vhci_hc_driverのhub_Control、vhci-hcdの例はvhciです.hub_コントロール()関数:
hub.c hub_event -> hub_port_init -> usb_control_msg -> usb_alloc_urb , usb_submit_urb vhci_urb_enqueue()
 hub.c , 。

 , usb_submit_urb ( “ hub” urb ):
usb_submit_urb
  ->usb_hcd_submit_urb(urb, mem_flags);
    ->rh_urb_enqueue // hub, is_root_hub() is true 
      ->rh_call_control 
        ->hub_control  // vhci-hcd vhci_hub_control
   |->status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);// root hub, U 、usb-skeleton.c urb enqueue

      vhci_hub_コントロール()関数は複雑です!「hcdフレームワーク」に応答してvirtual root hubポート番号がデバイス挿入を検出したことに応答することを目的とします.例えば、hub特有の要求に応じて対応するデータに応答したり、フラグを設定したふりをしたりします.
        GetHubDescriptor         DeviceRequest | USB_REQ_GET_DESCRIPTOR GetHubStatus GetPortStatus SetPortFeatureなどのhubリクエストをシミュレートすることで、「リアルなusb hubに近い」hubをシミュレートすることができます.そうしないと、「hcdフレームワーク」が明らかになり、hcdを仮想化できません.具体的に分析しないで、興味のある読者は研究することができて、分析はこれが必然的にusb coreの中のhcdにかかわるためです.cとhub.cこの2つの硬い骨は、私はやはりテーマに戻ります.vhci-hcd駆動自体を続けます.
問題があります.root hubがusbデバイスの挿入を検出した場合、最後にどのようにUディスク駆動をロードしますか?簡単なコードウォークをして、列挙したPID/VID情報をUディスク駆動やHIDマウス駆動などにマッチングすることで、対応する駆動のprobe駆動入口をコールバックし、最後に/dev/sdaまたは/dev/input/even 0を見ることができます.
rh_port_connect
  ->usb_hcd_poll_rh_status //hcd.c
    ->hcd->driver->hub_status_data(hcd, buffer)//vhci_hub_status
    ->usb_hcd_unlink_urb_from_ep(hcd, urb);
    ->usb_hcd_giveback_urb(hcd, urb, 0)
         ->usb_giveback_urb_bh();//tasklet_hi_schedule(&bh->bh);
            ->__usb_hcd_giveback_urb(urb);
              ->urb->complete(urb);//hub_irq
                ->hub_irq //hub.c  usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
                  ->kick_hub_wq(hub);
                    ->hub_event //INIT_WORK(&hub->events, hub_event);
                      ->port_event(hub, i);
                        ->hub_port_connect_change
                          ->hub_port_connect
                            ->hub_port_init
                            ->usb_new_device(udev);
                              ->usb_enumerate_device(udev);// 
                              ->device_add(&udev->dev);// 

device_add関数は、バスの通知チェーンから通知を送信、最終的にはバスのmatchメソッドusbデバイスと、matchが起動すると駆動のdrvwrapを呼び出す.driver.probe方法:デバイスであればdriverを通過する.cのusb_register_device_driver関数呼び出しusb_probe_デバイスメソッドはインタフェースであればdriverを通過する.cのusb_register_driver関数呼び出しusb_probe_interfaceメソッドは、Uディスクアクセスと仮定してmass_を呼び出すstorage駆動probe、probeでusb_を使用alloc_urbはurbを割り当て、最後にusb_submit_urbはurbをコミットします.バインドmass_に進みますstorage駆動hcdのurb_Enqueue、ここではvhci-hcdなのでvhci_に入りますurb_Enqueue()コールバック.     
最も重要な関数が着きました!
struct hc_ですdriver vhci_hc_driverのurb_Enqueueコールバック.すなわちvhci_urb_Enqueue()は、前述の文章でも簡単に文字で説明しましたが、UディスクドライブまたはUSBスケルトンドライブ(usb-skeleton.c)は、urbの作成と提出(usb_submit_urb)から離れられません.
static inline void usb_fill_bulk_urb(struct urb *urb,
				     struct usb_device *dev,
				     unsigned int pipe,
				     void *transfer_buffer,
				     int buffer_length,
				     usb_complete_t complete_fn,
				     void *context)
{
	urb->dev = dev;
	urb->pipe = pipe;
	urb->transfer_buffer = transfer_buffer;
	urb->transfer_buffer_length = buffer_length;
	urb->complete = complete_fn;
	urb->context = context;
}

// , usb-skeleton.c
        urb = usb_alloc_urb(0, GFP_KERNEL);
	...
	usb_fill_bulk_urb(urb, dev->udev,... );
	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
        ...
	retval = usb_submit_urb(urb, GFP_KERNEL);

usb_submit_urb()は非同期関数、usb_fill_bulk_urb()には登録完了関数があり、urbコミット後usb_submit_urb()はすぐに戻り、usbデバイスの処理が完了するとusb coreはurb->complete()に戻ります.
追跡usb_submit_urb()コードは、urbが最終的にhcdドライバに登録されたことを発見した.urb_Enqueue、私たちvhci-hcdにとってstatic int vhci_urb_Enqueue(struct usb_hcd*hcd,struct urb*urb,gfp_t mem_flags)は、struct urb*urbパラメータを見ましたか?このurbは上層駆動(usb-skeleton.cまたはUディスク駆動、hidマウス駆動など)から伝わったものである.すなわち,このurbにはusb通信データが含まれており,我々はusb共有(透過)を行い,第一歩は上位駆動のusb通信データをキャプチャし,tcpを介して送信することである.
      vhci_urb_Enqueue()関数で重要な関数は、usb_です.hcd_link_urb_to_ep(hcd,urb)とvhci_tx_urb(urb, vdev),usb_hcd_link_urb_to_ep()はusb coreのapiであり,hcdホストコントローラがurbをエンドポイントキューに入れ,下位送信を待つことを目的とする.vhci_tx_urb()はstruct vhci_を割り当てることによってprivインスタンス、urbを入力してpriv_に追加txキューは、最後に送信スレッド(すなわち、上述したvhci_tx_loop()を起動して送信する.送信スレッドはこちらがdequeue_from_priv_txはprivからtxキューはurbを抽出し、別のpriv_に挿入するrxキューでは、最後にurbをIPで送信する(USBIP_CMD_SUBMIT).詳細は、urbから有用なデータをどのように取得し、USBIPに記入するかを自分で読んでください.CMD_SUBMITコマンドのフィールドにあります.
受信スレッド(前述のvhci_rx_loop()はpickup_urb_and_free_priv、priv_からrxキューでは、フレーム番号に一致するurbを抽出し(送信時と受信時は同じurbオブジェクトを使用するので、seqnumを利用して以前送信時に使用したurbを取り戻すには、「返信ID」の概念に似ている)、priv_rxのノード.続いて、socketでurbを受信し、受信が完了するとusb_を呼び出すhcd_unlink_urb_from_ep()とusb_hcd_giveback_urb()urbをusb coreからデバイスドライバに返し、urbの処理を完了します.
static void vhci_recv_ret_submit(struct vhci_device *vdev,
				 struct usbip_header *pdu)
{	
...
...
    spin_lock_irqsave(&vhci->lock, flags);
	usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci_hcd), urb);
	spin_unlock_irqrestore(&vhci->lock, flags);

	usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status);

	usbip_dbg_vhci_rx("Leave
"); }

返却後、usb coreはUディスク駆動、usb-skeletonにコールバックする.c等に登録された完了関数(urb->complete()).
urbの送信と受信プロセスは基本的に「生産者消費者モード」を使用するため、文字で説明し、コードを具体的に分析しないで、読者は自分で読むことができます.linuxカーネルのチェーンテーブル操作、例えばlist_move_tail()はurbオブジェクトを送信キューpriv_からtxから取り外し、受信キューpriv_に配置します.rx里.
また、制御転送フェーズ(0番エンドポイント使用)にあり、デバイスアドレスが0の場合、vhci_urb_Enqueue()では、この段階での特殊な「標準要求」(例えば、usbデバイスアドレスを割り当てるためのUSB_REQ_SET_ADDRESS要求など)は、遠位端のserverに送信されず、直接ローカルに処理され(goto no_need_xmit)、すぐにurbをUディスクドライブに返却する.
/*
 * The enumeration process is as follows;
 *
 *  1. Get_Descriptor request to DevAddrs(0) EndPoint(0)
 *     to get max packet length of default pipe
 *
 *  2. Set_Address request to DevAddr(0) EndPoint(0)
 *
 */
if (usb_pipedevice(urb->pipe) == 0) {
...
...
        switch (ctrlreq->bRequest) {
		case USB_REQ_SET_ADDRESS:
			/* set_address may come when a device is reset */
			dev_info(dev, "SetAddress Request (%d) to port %d
", ctrlreq->wValue, vdev->rhport); usb_put_dev(vdev->udev); vdev->udev = usb_get_dev(urb->dev); spin_lock(&vdev->ud.lock); vdev->ud.status = VDEV_ST_USED; spin_unlock(&vdev->ud.lock); if (urb->status == -EINPROGRESS) { /* This request is successfully completed. */ /* If not -EINPROGRESS, possibly unlinked. */ urb->status = 0; } goto no_need_xmit; case USB_REQ_GET_DESCRIPTOR: if (ctrlreq->wValue == cpu_to_le16(USB_DT_DEVICE << 8)) usbip_dbg_vhci_hc( "Not yet?:Get_Descriptor to device 0 (get max pipe size)
"); usb_put_dev(vdev->udev); vdev->udev = usb_get_dev(urb->dev); goto out; default: /* NOT REACHED */ dev_err(dev, "invalid request to devnum 0 bRequest %u, wValue %u
", ctrlreq->bRequest, ctrlreq->wValue); ret = -EINVAL; goto no_need_xmit; ... ... ... no_need_xmit: usb_hcd_unlink_urb_from_ep(hcd, urb); no_need_unlink: spin_unlock_irqrestore(&vhci->lock, flags); if (!ret) usb_hcd_giveback_urb(hcd, urb, urb->status); return ret; }

結局、私たちが操作しているのは、実は遠隔のUディスクであり、実際には、遠隔のUディスクが自分のhcdホストコントローラに挿入されたとき、「usbデバイスアドレスの割り当て」の段階を経て、この要求を遠隔に送信する必要はありません.
最後にまとめますusb_hcd_link_urb_to_ep()とusb_hcd_unlink_urb_from_ep()/usb_hcd_giveback_urb()こそ肝心!もちろん、urbはデータを取り出してUSBIPに記入します.CMD_SUBMITコマンドフィールド、およびSOcketからUSBIP_を受信RET_SUBMITのコマンドフィールドにurbをどのように入力するかにも注意が必要です.
次の文章ではusbip-hostについて説明します.ko駆動のソース分析.