Connect HID gadget device dynamically

Connecting HID gadget device statically from the beginning of this
service causes an issue on WHLK test. To prevent the issue, this
commit changes the HID gadget device handling as dynamic so that
the HID gadget device can be connected when this service has at
least one KVM client.

Tested: /dev/hidg0 and /dev/hidg1 created only when at least one
KVM client is connected.

Change-Id: I5f6596b9e4e297fb6b507000499fc041460659f7
Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com>
diff --git a/create_usbhid.sh b/create_usbhid.sh
index 6562991..d1fa4e0 100644
--- a/create_usbhid.sh
+++ b/create_usbhid.sh
@@ -1,135 +1,157 @@
 #!/bin/sh
 
-new_directory="/sys/kernel/config/usb_gadget/obmc_hid"
+hid_conf_directory="/sys/kernel/config/usb_gadget/obmc_hid"
+dev_name="1e6a0000.usb-vhub"
 
-if [ -e "${new_directory}" ]; then
-	exit 0
+create_hid() {
+    # create gadget
+    mkdir "${hid_conf_directory}"
+    cd "${hid_conf_directory}"
+
+    # add basic information
+    echo 0x0100 > bcdDevice
+    echo 0x0200 > bcdUSB
+    echo 0x0104 > idProduct		# Multifunction Composite Gadget
+    echo 0x1d6b > idVendor		# Linux Foundation
+
+    # create English locale
+    mkdir strings/0x409
+
+    echo "OpenBMC" > strings/0x409/manufacturer
+    echo "virtual_input" > strings/0x409/product
+    echo "OBMC0001" > strings/0x409/serialnumber
+
+    # Create HID keyboard function
+    mkdir functions/hid.0
+
+    echo 1 > functions/hid.0/protocol	# 1: keyboard
+    echo 8 > functions/hid.0/report_length
+    echo 1 > functions/hid.0/subclass
+
+    # Binary HID keyboard descriptor
+    #  0x05, 0x01, // USAGE_PAGE (Generic Desktop)
+    #  0x09, 0x06, // USAGE (Keyboard)
+    #  0xa1, 0x01, // COLLECTION (Application)
+    #  0x05, 0x07, //   USAGE_PAGE (Keyboard)
+    #  0x19, 0xe0, //   USAGE_MINIMUM (Keyboard LeftControl)
+    #  0x29, 0xe7, //   USAGE_MAXIMUM (Keyboard Right GUI)
+    #  0x15, 0x00, //   LOGICAL_MINIMUM (0)
+    #  0x25, 0x01, //   LOGICAL_MAXIMUM (1)
+    #  0x75, 0x01, //   REPORT_SIZE (1)
+    #  0x95, 0x08, //   REPORT_COUNT (8)
+    #  0x81, 0x02, //   INPUT (Data,Var,Abs)
+    #  0x95, 0x01, //   REPORT_COUNT (1)
+    #  0x75, 0x08, //   REPORT_SIZE (8)
+    #  0x81, 0x03, //   INPUT (Data,Var,Abs)
+    #  0x95, 0x05, //   REPORT_COUNT (5)
+    #  0x75, 0x01, //   REPORT_SIZE (1)
+    #  0x05, 0x08, //   USAGE_PAGE (LEDs)
+    #  0x19, 0x01, //   USAGE_MINIMUM (Num Lock)
+    #  0x29, 0x05, //   USAGE_MAXIMUM (Kana)
+    #  0x91, 0x02, //   OUTPUT (Data,Var,Abs)
+    #  0x95, 0x01, //   REPORT_COUNT (1)
+    #  0x75, 0x03, //   REPORT_SIZE (3)
+    #  0x91, 0x03, //   OUTPUT (Cnst,Var,Abs)
+    #  0x95, 0x06, //   REPORT_COUNT (6)
+    #  0x75, 0x08, //   REPORT_SIZE (8)
+    #  0x15, 0x00, //   LOGICAL_MINIMUM (0)
+    #  0x25, 0x65, //   LOGICAL_MAXIMUM (101)
+    #  0x05, 0x07, //   USAGE_PAGE (Keyboard)
+    #  0x19, 0x00, //   USAGE_MINIMUM (Reserved (no event indicated))
+    #  0x29, 0x65, //   USAGE_MAXIMUM (Keyboard Application)
+    #  0x81, 0x00, //   INPUT (Data,Ary,Abs)
+    #  0xc0        // END_COLLECTION
+    echo -ne '\x05\x01\x09\x06\xa1\x01\x05\x07\x19\xe0\x29\xe7\x15\x00\x25\x01\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x03\x95\x05\x75\x01\x05\x08\x19\x01\x29\x05\x91\x02\x95\x01\x75\x03\x91\x03\x95\x06\x75\x08\x15\x00\x25\x65\x05\x07\x19\x00\x29\x65\x81\x00\xc0' > functions/hid.0/report_desc
+
+    # Create HID mouse function
+    mkdir functions/hid.1
+
+    echo 2 > functions/hid.1/protocol	# 2: mouse
+    echo 5 > functions/hid.1/report_length
+    echo 1 > functions/hid.1/subclass
+
+    # Binary HID mouse descriptor (absolute coordinate)
+    #  0x05, 0x01,       // USAGE_PAGE (Generic Desktop)
+    #  0x09, 0x02,       // USAGE (Mouse)
+    #  0xa1, 0x01,       // COLLECTION (Application)
+    #  0x09, 0x01,       //   USAGE (Pointer)
+    #  0xa1, 0x00,       //   COLLECTION (Physical)
+    #  0x05, 0x09,       //     USAGE_PAGE (Button)
+    #  0x19, 0x01,       //     USAGE_MINIMUM (Button 1)
+    #  0x29, 0x03,       //     USAGE_MAXIMUM (Button 3)
+    #  0x15, 0x00,       //     LOGICAL_MINIMUM (0)
+    #  0x25, 0x01,       //     LOGICAL_MAXIMUM (1)
+    #  0x95, 0x03,       //     REPORT_COUNT (3)
+    #  0x75, 0x01,       //     REPORT_SIZE (1)
+    #  0x81, 0x02,       //     INPUT (Data,Var,Abs)
+    #  0x95, 0x01,       //     REPORT_COUNT (1)
+    #  0x75, 0x05,       //     REPORT_SIZE (5)
+    #  0x81, 0x03,       //     INPUT (Cnst,Var,Abs)
+    #  0x05, 0x01,       //     USAGE_PAGE (Generic Desktop)
+    #  0x09, 0x30,       //     USAGE (X)
+    #  0x09, 0x31,       //     USAGE (Y)
+    #  0x35, 0x00,       //     PHYSICAL_MINIMUM (0)
+    #  0x46, 0xff, 0x7f, //     PHYSICAL_MAXIMUM (32767)
+    #  0x15, 0x00,       //     LOGICAL_MINIMUM (0)
+    #  0x26, 0xff, 0x7f, //     LOGICAL_MAXIMUM (32767)
+    #  0x65, 0x11,       //     UNIT (SI Lin:Distance)
+    #  0x55, 0x00,       //     UNIT_EXPONENT (0)
+    #  0x75, 0x10,       //     REPORT_SIZE (16)
+    #  0x95, 0x02,       //     REPORT_COUNT (2)
+    #  0x81, 0x02,       //     INPUT (Data,Var,Abs)
+    #  0xc0,             //   END_COLLECTION
+    #  0xc0              // END_COLLECTION
+    echo -ne '\x05\x01\x09\x02\xa1\x01\x09\x01\xa1\x00\x05\x09\x19\x01\x29\x03\x15\x00\x25\x01\x95\x03\x75\x01\x81\x02\x95\x01\x75\x05\x81\x03\x05\x01\x09\x30\x09\x31\x35\x00\x46\xff\x7f\x15\x00\x26\xff\x7f\x65\x11\x55\x00\x75\x10\x95\x02\x81\x02\xc0\xc0' > functions/hid.1/report_desc
+
+    # Create configuration
+    mkdir configs/c.1
+    mkdir configs/c.1/strings/0x409
+
+    echo 0x80 > configs/c.1/bmAttributes
+    echo 200 > configs/c.1/MaxPower
+    echo "" > configs/c.1/strings/0x409/configuration
+
+    # Link HID functions to configuration
+    ln -s functions/hid.0 configs/c.1
+    ln -s functions/hid.1 configs/c.1
+}
+
+connect_hid() {
+    if ! [[ `cat UDC` =~ "${dev_name}:p" ]]; then
+        i=0
+        num_ports=5
+        base_usb_dir="/sys/bus/platform/devices/${dev_name}/${dev_name}:p"
+        while [ $i -lt $num_ports ]; do
+            port=$(($i + 1))
+            i=$port
+            if [ ! -e "${base_usb_dir}${port}/gadget/suspended" ]; then
+                break
+            fi
+        done
+        echo "${dev_name}:p${port}" > UDC
+    fi
+}
+
+disconnect_hid() {
+    if [[ `cat UDC` =~ "${dev_name}:p" ]]; then
+        echo "" > UDC
+    fi
+}
+
+original_directory="$(pwd)"
+
+if [ ! -e "${hid_conf_directory}" ]; then
+    create_hid
+else
+    cd "${hid_conf_directory}"
 fi
 
-# create gadget
-original_directory="$(pwd)"
-mkdir "${new_directory}"
-cd "${new_directory}"
-
-# add basic information
-echo 0x0100 > bcdDevice
-echo 0x0200 > bcdUSB
-echo 0x0104 > idProduct		# Multifunction Composite Gadget
-echo 0x1d6b > idVendor		# Linux Foundation
-
-# create English locale
-mkdir strings/0x409
-
-echo "OpenBMC" > strings/0x409/manufacturer
-echo "virtual_input" > strings/0x409/product
-echo "OBMC0001" > strings/0x409/serialnumber
-
-# Create HID keyboard function
-mkdir functions/hid.0
-
-echo 1 > functions/hid.0/protocol	# 1: keyboard
-echo 8 > functions/hid.0/report_length
-echo 1 > functions/hid.0/subclass
-
-# Binary HID keyboard descriptor
-#  0x05, 0x01, // USAGE_PAGE (Generic Desktop)
-#  0x09, 0x06, // USAGE (Keyboard)
-#  0xa1, 0x01, // COLLECTION (Application)
-#  0x05, 0x07, //   USAGE_PAGE (Keyboard)
-#  0x19, 0xe0, //   USAGE_MINIMUM (Keyboard LeftControl)
-#  0x29, 0xe7, //   USAGE_MAXIMUM (Keyboard Right GUI)
-#  0x15, 0x00, //   LOGICAL_MINIMUM (0)
-#  0x25, 0x01, //   LOGICAL_MAXIMUM (1)
-#  0x75, 0x01, //   REPORT_SIZE (1)
-#  0x95, 0x08, //   REPORT_COUNT (8)
-#  0x81, 0x02, //   INPUT (Data,Var,Abs)
-#  0x95, 0x01, //   REPORT_COUNT (1)
-#  0x75, 0x08, //   REPORT_SIZE (8)
-#  0x81, 0x03, //   INPUT (Data,Var,Abs)
-#  0x95, 0x05, //   REPORT_COUNT (5)
-#  0x75, 0x01, //   REPORT_SIZE (1)
-#  0x05, 0x08, //   USAGE_PAGE (LEDs)
-#  0x19, 0x01, //   USAGE_MINIMUM (Num Lock)
-#  0x29, 0x05, //   USAGE_MAXIMUM (Kana)
-#  0x91, 0x02, //   OUTPUT (Data,Var,Abs)
-#  0x95, 0x01, //   REPORT_COUNT (1)
-#  0x75, 0x03, //   REPORT_SIZE (3)
-#  0x91, 0x03, //   OUTPUT (Cnst,Var,Abs)
-#  0x95, 0x06, //   REPORT_COUNT (6)
-#  0x75, 0x08, //   REPORT_SIZE (8)
-#  0x15, 0x00, //   LOGICAL_MINIMUM (0)
-#  0x25, 0x65, //   LOGICAL_MAXIMUM (101)
-#  0x05, 0x07, //   USAGE_PAGE (Keyboard)
-#  0x19, 0x00, //   USAGE_MINIMUM (Reserved (no event indicated))
-#  0x29, 0x65, //   USAGE_MAXIMUM (Keyboard Application)
-#  0x81, 0x00, //   INPUT (Data,Ary,Abs)
-#  0xc0        // END_COLLECTION
-echo -ne '\x05\x01\x09\x06\xa1\x01\x05\x07\x19\xe0\x29\xe7\x15\x00\x25\x01\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x03\x95\x05\x75\x01\x05\x08\x19\x01\x29\x05\x91\x02\x95\x01\x75\x03\x91\x03\x95\x06\x75\x08\x15\x00\x25\x65\x05\x07\x19\x00\x29\x65\x81\x00\xc0' > functions/hid.0/report_desc
-
-# Create HID mouse function
-mkdir functions/hid.1
-
-echo 2 > functions/hid.1/protocol	# 2: mouse
-echo 5 > functions/hid.1/report_length
-echo 1 > functions/hid.1/subclass
-
-# Binary HID mouse descriptor (absolute coordinate)
-#  0x05, 0x01,       // USAGE_PAGE (Generic Desktop)
-#  0x09, 0x02,       // USAGE (Mouse)
-#  0xa1, 0x01,       // COLLECTION (Application)
-#  0x09, 0x01,       //   USAGE (Pointer)
-#  0xa1, 0x00,       //   COLLECTION (Physical)
-#  0x05, 0x09,       //     USAGE_PAGE (Button)
-#  0x19, 0x01,       //     USAGE_MINIMUM (Button 1)
-#  0x29, 0x03,       //     USAGE_MAXIMUM (Button 3)
-#  0x15, 0x00,       //     LOGICAL_MINIMUM (0)
-#  0x25, 0x01,       //     LOGICAL_MAXIMUM (1)
-#  0x95, 0x03,       //     REPORT_COUNT (3)
-#  0x75, 0x01,       //     REPORT_SIZE (1)
-#  0x81, 0x02,       //     INPUT (Data,Var,Abs)
-#  0x95, 0x01,       //     REPORT_COUNT (1)
-#  0x75, 0x05,       //     REPORT_SIZE (5)
-#  0x81, 0x03,       //     INPUT (Cnst,Var,Abs)
-#  0x05, 0x01,       //     USAGE_PAGE (Generic Desktop)
-#  0x09, 0x30,       //     USAGE (X)
-#  0x09, 0x31,       //     USAGE (Y)
-#  0x35, 0x00,       //     PHYSICAL_MINIMUM (0)
-#  0x46, 0xff, 0x7f, //     PHYSICAL_MAXIMUM (32767)
-#  0x15, 0x00,       //     LOGICAL_MINIMUM (0)
-#  0x26, 0xff, 0x7f, //     LOGICAL_MAXIMUM (32767)
-#  0x65, 0x11,       //     UNIT (SI Lin:Distance)
-#  0x55, 0x00,       //     UNIT_EXPONENT (0)
-#  0x75, 0x10,       //     REPORT_SIZE (16)
-#  0x95, 0x02,       //     REPORT_COUNT (2)
-#  0x81, 0x02,       //     INPUT (Data,Var,Abs)
-#  0xc0,             //   END_COLLECTION
-#  0xc0              // END_COLLECTION
-echo -ne '\x05\x01\x09\x02\xa1\x01\x09\x01\xa1\x00\x05\x09\x19\x01\x29\x03\x15\x00\x25\x01\x95\x03\x75\x01\x81\x02\x95\x01\x75\x05\x81\x03\x05\x01\x09\x30\x09\x31\x35\x00\x46\xff\x7f\x15\x00\x26\xff\x7f\x65\x11\x55\x00\x75\x10\x95\x02\x81\x02\xc0\xc0' > functions/hid.1/report_desc
-
-# Create configuration
-mkdir configs/c.1
-mkdir configs/c.1/strings/0x409
-
-echo 0x80 > configs/c.1/bmAttributes
-echo 200 > configs/c.1/MaxPower
-echo "" > configs/c.1/strings/0x409/configuration
-
-# Link HID functions to configuration
-ln -s functions/hid.0 configs/c.1
-ln -s functions/hid.1 configs/c.1
-
-# Enable gadget
-dev_name="1e6a0000.usb-vhub"
-i=0
-num_ports=5
-base_usb_dir="/sys/bus/platform/devices/${dev_name}/${dev_name}:p"
-while [ $i -lt $num_ports ]; do
-	port=$(($i + 1))
-	i=$port
-	if [ ! -e "${base_usb_dir}${port}/gadget/suspended" ]; then
-		break
-	fi
-done
-echo "${dev_name}:p${port}" > UDC
+if [ "$1" = "connect" ]; then
+    connect_hid
+elif [ "$1" = "disconnect" ]; then
+    disconnect_hid
+else
+    echo "Invalid option: $1. Use 'connect' or 'disconnect'."
+fi
 
 cd "${original_directory}"
diff --git a/ikvm_input.cpp b/ikvm_input.cpp
index df12f27..c4cce50 100644
--- a/ikvm_input.cpp
+++ b/ikvm_input.cpp
@@ -16,6 +16,8 @@
 
 #include "scancodes.hpp"
 
+namespace fs = std::filesystem;
+
 namespace ikvm
 {
 
@@ -27,6 +29,54 @@
     keyboardReport{0}, pointerReport{0}, keyboardPath(kbdPath),
     pointerPath(ptrPath)
 {
+    hidUdcStream.exceptions(std::ofstream::failbit | std::ofstream::badbit);
+    hidUdcStream.open(hidUdcPath, std::ios::out | std::ios::app);
+}
+
+Input::~Input()
+{
+    if (keyboardFd >= 0)
+    {
+        close(keyboardFd);
+    }
+
+    if (pointerFd >= 0)
+    {
+        close(pointerFd);
+    }
+
+    disconnect();
+    hidUdcStream.close();
+}
+
+void Input::connect()
+{
+    try
+    {
+        for (const auto& port : fs::directory_iterator(usbVirtualHubPath))
+        {
+            if (fs::is_directory(port) && !fs::is_symlink(port) &&
+                !fs::exists(port.path() / "gadget/suspended"))
+            {
+                const std::string portId = port.path().filename();
+                hidUdcStream << portId << std::endl;
+                break;
+            }
+        }
+    }
+    catch (fs::filesystem_error& e)
+    {
+        log<level::ERR>("Failed to search USB virtual hub port",
+                        entry("ERROR=%s", e.what()));
+        return;
+    }
+    catch (std::ofstream::failure& e)
+    {
+        log<level::ERR>("Failed to connect HID gadget",
+                        entry("ERROR=%s", e.what()));
+        return;
+    }
+
     if (!keyboardPath.empty())
     {
         keyboardFd = open(keyboardPath.c_str(), O_RDWR | O_CLOEXEC);
@@ -56,16 +106,28 @@
     }
 }
 
-Input::~Input()
+void Input::disconnect()
 {
     if (keyboardFd >= 0)
     {
         close(keyboardFd);
+        keyboardFd = -1;
     }
 
     if (pointerFd >= 0)
     {
         close(pointerFd);
+        pointerFd = -1;
+    }
+
+    try
+    {
+        hidUdcStream << "" << std::endl;
+    }
+    catch (std::ofstream::failure& e)
+    {
+        log<level::ERR>("Failed to disconnect HID gadget",
+                        entry("ERROR=%s", e.what()));
     }
 }
 
diff --git a/ikvm_input.hpp b/ikvm_input.hpp
index 2adc7c1..aae7cef 100644
--- a/ikvm_input.hpp
+++ b/ikvm_input.hpp
@@ -2,6 +2,8 @@
 
 #include <rfb/rfb.h>
 
+#include <filesystem>
+#include <fstream>
 #include <map>
 #include <string>
 
@@ -29,6 +31,10 @@
     Input(Input&&) = default;
     Input& operator=(Input&&) = default;
 
+    /* @brief Connects HID gadget to host */
+    void connect();
+    /* @brief Disconnects HID gadget from host */
+    void disconnect();
     /*
      * @brief RFB client key event handler
      *
@@ -72,6 +78,12 @@
         0x04, // left alt
         0x40  // right alt
     };
+    /* @brief Path to the HID gadget UDC */
+    static constexpr const char* hidUdcPath =
+        "/sys/kernel/config/usb_gadget/obmc_hid/UDC";
+    /* @brief Path to the USB virtual hub */
+    static constexpr const char* usbVirtualHubPath =
+        "/sys/bus/platform/devices/1e6a0000.usb-vhub";
     /*
      * @brief Translates a RFB-specific key code to HID modifier bit
      *
@@ -109,6 +121,8 @@
      *        of which keys are down
      */
     std::map<int, int> keysDown;
+    /* @brief Handle of the HID gadget UDC */
+    std::ofstream hidUdcStream;
 };
 
 } // namespace ikvm
diff --git a/ikvm_server.cpp b/ikvm_server.cpp
index ebeaef0..0736d1f 100644
--- a/ikvm_server.cpp
+++ b/ikvm_server.cpp
@@ -178,6 +178,7 @@
 
     if (server->numClients-- == 1)
     {
+        server->input.disconnect();
         rfbMarkRectAsModified(server->server, 0, 0, server->video.getWidth(),
                               server->video.getHeight());
     }
@@ -193,6 +194,7 @@
     cl->clientFramebufferUpdateRequestHook = clientFramebufferUpdateRequest;
     if (!server->numClients++)
     {
+        server->input.connect();
         server->pendingResize = false;
         server->frameCounter = 0;
     }
diff --git a/start-ipkvm.service b/start-ipkvm.service
index 5f945b3..60234b2 100644
--- a/start-ipkvm.service
+++ b/start-ipkvm.service
@@ -4,7 +4,7 @@
 
 [Service]
 Restart=always
-ExecStartPre=/usr/bin/create_usbhid.sh
+ExecStartPre=/usr/bin/create_usbhid.sh disconnect
 ExecStart=/usr/bin/obmc-ikvm -v /dev/video0 -k /dev/hidg0 -p /dev/hidg1
 
 [Install]