2017年10月20日 星期五

Embedded Linux as USB Device : Implemention of Keyboard and Mouse Combo



     Embedded Linux could be the USB device, if the kernel support the USB gadget. But the USB-HID(human-interface device) report descriptor does not exist in kernel code, developer needs to fill it by himself.
     This article provide a descriptor to support Keyboard and Mouse at the same time. I think it is a good entry point for everyone interested in how to configurate his embedded-linux as an USB device.

     My development board is based on Hi3516a, a chip featuring H264/H265 hardware encoding from China army.

  零. Build the necessary kernel modules

    In the menuconfig of kernel, you need to set the selections as Module(M):
Device Drivers  -> USB support -> your_system_name_usb2.0 usb device support -> your_chip (Maybe it has been built into kernel image)
  Device Drivers  -> USB support -> USB Gadget Support ->HID Gadget

Save and build the kernel modules:
  make XCOMPILER_OR_CONFIG  menuconfig

You will find the (dwc_otg.ko and) g_hid.ko has been generated.
Insert the module(s) to your embedded Linux, you may encounter that :


insmod: error inserting './g_hid.ko': -1 No such device

That is, the hid.c file in your_embedded_kernel_folder/drivers/usb/gadget/hid.c is not complete, the platform_device has not been implemented yet.

一. Implement the keyboard and mouse combo  in hid.c file :

Add below lines at about line 265 in file hid.c :


MODULE_LICENSE("GPL");


/*hid descriptor for keyboard and mouse*/
static struct hidg_func_descriptor generic_keyboard_and_mouse_combo_data = {
 .subclass = 0, /*NO SubClass*/
 .protocol = 3, /*Keyboard and mouse*/ /*the number is unimportant*/
 .report_length = 9,
 .report_desc_length    = 121,
 .report_desc        = {

  0x05, 0x01, // USAGE_PAGE (Generic Desktop)

  0x09, 0x06, // USAGE (Keyboard)

  0xa1, 0x01, // COLLECTION (Application)

  0x85, 0x01, //Report ID (1)

  0x05, 0x07, //     USAGE_PAGE (Keyboard/Keypad)

  0x19, 0xe0, //     USAGE_MINIMUM (Keyboard LeftControl)
  0x29, 0xe7, //     USAGE_MAXIMUM (Keyboard Right GUI)
  0x15, 0x00, //     LOGICAL_MINIMUM (0)
  0x25, 0x01, //     LOGICAL_MAXIMUM (1)
  0x95, 0x08, //     REPORT_COUNT (8)
  0x75, 0x01, //     REPORT_SIZE (1)
  0x81, 0x02, //     INPUT (Data,Var,Abs)

  0x95, 0x01, //     REPORT_COUNT (1)
  0x75, 0x08, //     REPORT_SIZE (8)
  0x81, 0x03, //     INPUT (Cnst,Var,Abs)


  0x95, 0x06, //   REPORT_COUNT (6)
  0x75, 0x08, //   REPORT_SIZE (8)
  0x15, 0x00, //   LOGICAL_MINIMUM (0)
  0x25, 0xFF, //   LOGICAL_MAXIMUM (255)
  0x05, 0x07, //   USAGE_PAGE (Keyboard/Keypad)
  0x19, 0x00, //   USAGE_MINIMUM (Reserved (no event indicated))
  0x29, 0x65, //   USAGE_MAXIMUM (Keyboard Application)
  0x81, 0x00, //     INPUT (Data,Ary,Abs)


  0x25, 0x01, //     LOGICAL_MAXIMUM (1)
  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)

  0xc0,        // END_COLLECTION



  0x05, 0x01, // USAGE_PAGE (Generic Desktop)
  0x09, 0x02, // USAGE (Mouse)
  0xa1, 0x01, // COLLECTION (Application)

  0x85, 0x02, //Report ID (2)

  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)
  0x09, 0x38, //     USAGE (Wheel)
  0x15, 0x81, //     LOGICAL_MINIMUM (-127)
  0x25, 0x7f, //     LOGICAL_MAXIMUM (127)
  0x75, 0x08, //     REPORT_SIZE (8)
  0x95, 0x03, //     REPORT_COUNT (3)
  0x81, 0x06, //     INPUT (Data,Var,Rel)

  0xc0,       //   END_COLLECTION

  0xc0        // END_COLLECTION
 }/*report_desc*/
};


static struct platform_device generic_hid_keyboard_and_mouse_combo = {
    .name = "hidg",
    .id            = 1,
    .num_resources = 0,
    .resource    = 0,
    .dev.platform_data = &generic_keyboard_and_mouse_combo_data,
};


static int __init hidg_init(void)
{
 int status;

#if(1)
 status = platform_device_register(&generic_hid_keyboard_and_mouse_combo);
 if (status < 0)
 {
  printk("platform_driver hid :*****wrong\n");
  platform_device_unregister(&generic_hid_keyboard_and_mouse_combo);
  return status;
 }
#endif


 status = platform_driver_probe(&hidg_plat_driver,
    hidg_plat_driver_probe);
 if (status < 0)
  return status;

 status = usb_composite_probe(&hidg_driver, hid_bind);
 if (status < 0)
  platform_driver_unregister(&hidg_plat_driver);

 return status;
}

module_init(hidg_init);


static void __exit hidg_cleanup(void)
{
 platform_driver_unregister(&hidg_plat_driver);

#if(1)
 platform_device_unregister(&generic_hid_keyboard_and_mouse_combo);
#endif

 usb_composite_unregister(&hidg_driver);
}
module_exit(hidg_cleanup);


Rebuild and insert the generated g_hid.ko into your embedded linux, there should be no error (maybe with warnings).
Under the  /dev folder, there should be a device represents gadget-hid:


~ # ls /dev/hid*
/dev/hidg0



 二. About the byte array format :
  For input (device to host)

    Byte 0: indicate it is keyboard (1) or mouse(2) command
 
  Keyboard (byte zeroth = 1)
    Byte 1 : external scancodes( left ctrl, left shift, left alt, left meta,  right ctrl, right shift, right alt and right meta) represent the bit 0-7, from scancode 0xe0 to e0xe7. 1 as pressed, 0 as released.
   Byte 2 : reserved.
   Byte 3 to  byte 8 : the remained scancode pressed.  If there are all zero, means all keys are released.

  Mouse  (byte zeroth = 2)
   Byte 1 : bit 0 represents left button, bit 1 represents right button, and bit 2 represents middle button. 1 means pressed, 0 means released.
   Byte 2 : horizontal moving, from -127 (to left) to 127( to right).
   Byte 3: vertical moving, from -127(to up) to 127(to bottom).
   Byte 4: scrolling, from -127(scrolling down) to 127(scrolling up).

For output (host to device)
   Byte 0: indicate it is for keyboard (1). (mouse does not received any message from host)
   Byte 1: Indicate the led states : bit 0 is Num lock, bit 1 is Caps lock, and bit 2 is Scr lock. 1 means the led should be on, and 0 means it should be off.

三.  Implement a command line program to simulate the keyboard and mouse, the code be :

main_keyboard_and_mouse.c

#include <string.h>
#include <stdio.h>
#include <ctype.h>

#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <inttypes.h>

#include <fcntl.h>

#include <termios.h>
#include <signal.h>

//#include <byteswap.h>

//
// gist.github.com/MightyPork/6da26e382a7ad91b5496ee55fdc73db2#file-usb_hid_keys-h-L259
//

#define KEYBOARD_REPORT_LEN     (8)
#define MOUSE_REPORT_LEN     (4)

#define TOTAL_REPORT_LEN     (9)


#define KEYBOARD_ID       (1)
#define MOUSE_ID       (2)

int SetTerminal(int is_show_input, int is_block)
{
 struct termios tio;
 int file_status;

 tcgetattr(STDIN_FILENO, &tio);

 if(0 == is_show_input)
  tio.c_lflag &= ~(ICANON | ECHO);
 else
  tio.c_lflag |= (ICANON | ECHO);


 tcsetattr(STDIN_FILENO, TCSANOW, &tio);


 if(0 == is_block)
 {
  file_status = fcntl(STDIN_FILENO, F_GETFL, 0);
  file_status |= O_NONBLOCK;
  fcntl(STDIN_FILENO, F_SETFL, file_status);
 }
 else
 {
  file_status = fcntl(STDIN_FILENO, F_GETFL, 0);
  file_status &= ~O_NONBLOCK;
  fcntl(STDIN_FILENO, F_SETFL, file_status);
 }/*if is_block */

 return 0;
}/*SetTerminal*/


int IsKeyboardHit(void)
{
#if(1)
 int ch; 

 ch = getchar();

 if(ch != EOF)
 {
  ungetc(ch, stdin);
  return 1;
 }/*if */

 return 0;
#else
 struct timeval tv;
 fd_set rdfs;

 tv.tv_sec = 0;
 tv.tv_usec = 1000;

 FD_ZERO(&rdfs);
 FD_SET (STDIN_FILENO, &rdfs);

 select(STDIN_FILENO + 1, &rdfs, NULL, NULL, &tv);
 return FD_ISSET(STDIN_FILENO, &rdfs);
#endif
}/*IsKeyboardHit*/


int ToMouseModeData(int character, unsigned char *p_send_buffer)
{
  int c;
  c = tolower(character);

  p_send_buffer[0] = MOUSE_ID;

  switch(c)
  {
#define MOVE_MULTIPLY_CONSTANT    (5)
   case 'a':
    p_send_buffer[2] = -MOVE_MULTIPLY_CONSTANT;
    break;

   case 'd':
    p_send_buffer[2] = MOVE_MULTIPLY_CONSTANT;
   break;

   case 'w':
    p_send_buffer[3] = -MOVE_MULTIPLY_CONSTANT;
    break;

   case 's':
    p_send_buffer[3] = MOVE_MULTIPLY_CONSTANT;
    break;


   case 'f':
    p_send_buffer[1] ^= 0x01 << 0;
    break;
   case 'h':
    p_send_buffer[1] ^= 0x01 << 1;
    break;
   case 'g':
    p_send_buffer[1] ^= 0x01 << 2;
    break;

   case 't':
    p_send_buffer[4] = 1;
    break;

   case 'b':
    p_send_buffer[4] = -1;
    break;


   default:
    return -1;
  }/*switch*/

 return 0;
}/*ToMouseModeData*/


#ifndef _PC_TEST

int g_fd = -1;
pthread_t g_read_thread;
int g_is_read_thread_running = 0;
int g_is_end_program = 0;


void *ReadTheadRoutine(void *argv)
{
 g_is_read_thread_running = 1;

 while(0 == g_is_end_program)
 {
  
  struct timeval tv;
  fd_set rdfs;

  tv.tv_sec = 0;
  tv.tv_usec = 0;

  FD_ZERO(&rdfs);
  FD_SET (g_fd, &rdfs);

  select(g_fd + 1, &rdfs, NULL, NULL, &tv);

  if(FD_ISSET(g_fd, &rdfs))
  {
#define READ_BUFF_LEN     (4)
   uint8_t read_buff[READ_BUFF_LEN];
   ssize_t len;
   int i;

   if(-1 == g_fd)
    break;

   len = read(g_fd, &read_buff[0], READ_BUFF_LEN);
   printf("read : \r\n");
   for(i = 0; i < len; i++)
    printf("0x%02x ", read_buff[i]);
   printf("\r\n");

   if(2 <= len)
   {
    uint8_t keyboard_led_states;
    keyboard_led_states = read_buff[1];

    printf("NUM led ");
    if((0x01 << 0)& keyboard_led_states)
     printf("on\r\n");
    else
     printf("off\r\n");

    printf("CAPS lock led ");
    if((0x01 << 1) & keyboard_led_states)
     printf("on\r\n");
    else
     printf("off\r\n");

    printf("SCROLL lock led ");
    if((0x01 << 2)& keyboard_led_states)
     printf("on\r\n");
    else
     printf("off\r\n");

    if( ((~0x07) & keyboard_led_states) )
    {
     printf("the other leds on : 0x%02x\r\n", 
      ((~0x07) & keyboard_led_states));
    }
    printf("\r\n");
   }/*if received*/

  }/*if */

 }/* while 0 == g_is_end_program*/

 return (void*)0;
}/*ReadTheadRoutine*/

#endif



int ScanCodeToUSBKeyboardReport(unsigned char *p_scan_code, int scan_code_num,
 unsigned char *p_report_buff)
{
 int i, j;

 if(NULL == p_scan_code || NULL == p_report_buff)
  return -1;

 if(6 < scan_code_num)
  return -2;

 memset(&p_report_buff[0], 0, KEYBOARD_REPORT_LEN);

 for(i = 0; i < 8; i++){
  if(0xe0 == p_scan_code[i])
   p_report_buff[0] |= 0x01<< 0;
  if(0xe1 == p_scan_code[i])
   p_report_buff[0] |= 0x01<< 1;
  if(0xe2 == p_scan_code[i])
   p_report_buff[0] |= 0x01<< 2;
  if(0xe3 == p_scan_code[i])
   p_report_buff[0] |= 0x01<< 3;
  if(0xe4 == p_scan_code[i])
   p_report_buff[0] |= 0x01<< 4;
  if(0xe5 == p_scan_code[i])
   p_report_buff[0] |= 0x01<< 5;
  if(0xe6 == p_scan_code[i])
   p_report_buff[0] |= 0x01<< 6;
  if(0xe7 == p_scan_code[i])
   p_report_buff[0] |= 0x01<< 7;
 }/*for i*/

 //printf("p_report_buff[0] = 0x%02x\r\n", p_report_buff[0]);

 j = 2;
 for(i = 0; i < scan_code_num; i++){
  if(!(0xe0 <= p_scan_code[i] &&  0xe7 >= p_scan_code[i])){
   p_report_buff[j] = p_scan_code[i];
   j++;
   
  }/*if */
 }/*for i*/

 return 0;
}/*ScanCodeToUSBKeyboardReport*/


int ToKeyboardModeData(unsigned char *p_buffer, int buffer_len, 
 unsigned char *p_send_buffer)
{

#define NORMAL_KEY_NUM      (6)
 size_t sent_len;

 int i, j;
 char *p_temp;
 uint8_t scan_code[NORMAL_KEY_NUM];

 uint64_t read_hex;

 if(0 == p_buffer)
  return -1;


 if(2*sizeof(uint64_t) + 1 < strlen(p_buffer))
 {

  printf("input string is too long \r\n");
  return -2;
 }/*if */

 read_hex = strtoll(p_buffer, &p_temp, 16);

 if(p_temp == (char*)p_buffer)
 {
  printf("unknown scan_code = %s\r\n", p_buffer);
  return -3;
 }/*if */

 {
  uint8_t temp[KEYBOARD_REPORT_LEN - NORMAL_KEY_NUM];
  memcpy(&scan_code[0], (char*)&read_hex, NORMAL_KEY_NUM);

  memcpy(&temp[0], ((char*)&read_hex + NORMAL_KEY_NUM), 
   (8 - NORMAL_KEY_NUM));


  for(i = 0; i < KEYBOARD_REPORT_LEN - NORMAL_KEY_NUM; i++ ){
   if(0x0ff&temp[i])
   {
    printf("WARNING : max %d keys, truncate the lower\r\n", 
     NORMAL_KEY_NUM);
    break;
   }/*if */
  }/*for i*/

  for(i = 0; i < KEYBOARD_REPORT_LEN - NORMAL_KEY_NUM; i++ ){
   if(0x0ff&temp[i])
    scan_code[i] = temp[i];
  }/*for i*/
 }/*local variable*/

 for(i = 0; i< NORMAL_KEY_NUM; i++)
  printf("0x%02x ", scan_code[i]);
 printf("\r\n");

 p_send_buffer[0] = KEYBOARD_ID;

 ScanCodeToUSBKeyboardReport(&scan_code[0], NORMAL_KEY_NUM, 
  &p_send_buffer[1]);

 for(i = 0; i< TOTAL_REPORT_LEN; i++)
  printf("0x%02x ", p_send_buffer[i]);
 printf("\r\n");


 return 0;
}/*KeyboardModeRoutine*/



void InterruptSignalHandlingRoutine(int sig)
{
 unsigned char buff[TOTAL_REPORT_LEN];
 int i;

 printf("release all buttons\r\n");

#ifndef _PC_TEST
 if(-1 == g_fd)
  return ;

 if(0 != g_is_read_thread_running)
 {
  int ret;

  g_is_end_program = 1;
  pthread_join(g_read_thread, (void*)&ret);
 }/*if 0 != g_is_end_program */
#endif

 memset(&buff[0], 0, TOTAL_REPORT_LEN);
 buff[0] = KEYBOARD_ID;

 for(i = 0; i< TOTAL_REPORT_LEN; i++)
  printf("0x%02x ", buff[i]);
 printf("\r\n");

#ifndef _PC_TEST

 if(-1 != g_fd)
 {
  ssize_t sent_len;
  sent_len = write(g_fd, &buff[0], TOTAL_REPORT_LEN);
  //printf("sent_len = %d\r\n", sent_len);
 }/*if -1 != g_fd*/
#endif

 memset(&buff[0], 0, TOTAL_REPORT_LEN);
 buff[0] = MOUSE_ID;

 for(i = 0; i< TOTAL_REPORT_LEN; i++)
  printf("0x%02x ", buff[i]);
 printf("\r\n");

#ifndef _PC_TEST

 if(-1 != g_fd)
 {
  ssize_t sent_len;
  sent_len = write(g_fd, &buff[0], TOTAL_REPORT_LEN);
  //printf("sent_len = %d\r\n", sent_len);
  close(g_fd); g_fd = -1;
 }/*if -1 != g_fd*/
#endif

 SetTerminal(1, 1);
 exit(0);
}/*InterruptSignalHandlingRoutine*/

#define BUFF_SIZE      (256)

int main(int argc, char *argv[])
{
 char buffer[BUFF_SIZE];

 signal(SIGINT, InterruptSignalHandlingRoutine);
 int is_keyboard_mode;

#ifndef _PC_TEST
 if(argc < 2)
 {
  printf("/dev/hidgXX should be argument\r\n");
  return -1;
 }

 printf("argv[1] = %s\r\n", &argv[1][0]);
 g_fd = open(argv[1], O_RDWR, 0666);

 if(-1 == g_fd)
 {
  printf("%s open error\r\n", strerror(errno));
  return -2;
 }/*if */

 pthread_create(&g_read_thread, NULL, ReadTheadRoutine, &g_fd);
#endif

 is_keyboard_mode = 1;
 while(1)
 {
  unsigned char send_buffer[TOTAL_REPORT_LEN];
  memset(&buffer[0], 0, sizeof(buffer));
  memset(&send_buffer[0], 0, TOTAL_REPORT_LEN);
  if(0 != is_keyboard_mode)
  {
   if(fgets(&buffer[0], BUFF_SIZE, stdin))
   {
    {
     int i;
     int is_re_get_input;
     int input_len;

     input_len = strlen(&buffer[0]);
     is_re_get_input = 0;

     for(i = 0; i < input_len; i++){

      if( ' '== buffer[i])
       continue;

      if('m' == buffer[i] || 'M' == buffer[i])
      {
       is_keyboard_mode = 0;
       is_re_get_input = 1;


       printf("enter mouse mode :\r\n");
       printf("press k swithing to keyboard mode\r\n");
       SetTerminal(0, 1);

       break;
      }
      
     }/*for */

     if(0 != is_re_get_input)
      continue;
    }/* 0 != is_keyboard_mode */

    if(0 > ToKeyboardModeData(&buffer[0], BUFF_SIZE, &send_buffer[0]))
     continue;
   }
  }
  else /*if mouse*/
  {
   if(0 != IsKeyboardHit())
   {
    int c;
    c = getchar();
#if(1)
    if('k'== c || 'K' == c)
    {
     is_keyboard_mode = 1;
      SetTerminal(1, 1);
      printf("enter keyboard mode :\r\n");
      printf("press m swithing to mouse mode\r\n");
      continue;
    }

    if(0 > ToMouseModeData(c, &send_buffer[0]))
     continue;
#else
    c = tolower(c);

    send_buffer[0] = MOUSE_ID;

    switch(c)
    {
  #define MOVE_MULTIPLY_CONSTANT    (5)
     case 'a':
      send_buffer[2] = -MOVE_MULTIPLY_CONSTANT;
      break;

     case 'd':
      send_buffer[2] = MOVE_MULTIPLY_CONSTANT;
     break;

     case 'w':
      send_buffer[3] = -MOVE_MULTIPLY_CONSTANT;
      break;

     case 's':
      send_buffer[3] = MOVE_MULTIPLY_CONSTANT;
      break;


     case 'f':
      send_buffer[1] ^= 0x01 << 0;
      break;
     case 'h':
      send_buffer[1] ^= 0x01 << 1;
      break;
     case 'g':
      send_buffer[1] ^= 0x01 << 2;
      break;

     case 't':
      send_buffer[4] = 1;
      break;

     case 'b':
      send_buffer[4] = -1;
      break;


     case 'k':
      is_keyboard_mode = 1;
      SetTerminal(1, 1);
      printf("enter keyboard mode :\r\n");
      printf("press m swithing to mouse mode\r\n");
      continue;
     default:
      continue;
    }/*switch*/
#endif
   }/*keyboard is hit*/
  }/*if */


#ifndef _PC_TEST
  int sent_len;
  sent_len = write(g_fd, &send_buffer[0], TOTAL_REPORT_LEN);
  if(0 >= sent_len)
  {
   printf("sent_len = %d\r\n", sent_len);
   return 0;
  }
  usleep(30*1000);

  memset(&send_buffer[0], 0, TOTAL_REPORT_LEN);

  if(0 == is_keyboard_mode)
   send_buffer[0] = MOUSE_ID;
  else
   send_buffer[0] = KEYBOARD_ID;


  sent_len = write(g_fd, &send_buffer[0], TOTAL_REPORT_LEN);
  if(0 >= sent_len)
  {
   printf("sent_len = %d\r\n", sent_len);
   return 0;
  }
  printf("\r\n");
#endif
 }/*while */


#ifndef _PC_TEST
 close(g_fd); g_fd = 0;
#endif
 return 0;
}/*main*/


To build the code :


your_embedded_linux-gcc main_keyboard_and_mouse.c -lpthread -o  usb_keyboard_and_mouse_control


To run the code, the /dev/hidgX should be followed as the argument:


~ # ./usb_keyboard_and_mouse_control /dev/hidg0

   The default is keyboard mode, you could input the scancode  number directly on the embedded linux terminal while the program has run:
   The scancode map your could refer to this.

 Press m and press enter, you could switch to mouse mode.

In the mouse mode, your input would not should in the terminal, but the
programming receives it indeed.  key WASD as up, down, lef and right. Key D as left button, G as right button, F as middle. Key R as scrolling up, V as scrolling down.
press k , it would switch to keyboard mode immediately (it is not necessary to follow a enter after "k").


If you are interested in keyboard or mouse hid only instead of the combo one, there are files your could reter to.


Known insufficieny:

   If the embedded Linux does not plus into the host but the program run, the embedded Linux might be system crash or the program blocking at the writing /dev/hidgX. By default, there is no triggered event while the USB has connected/disconnected. Because USB devices' power would be supported by the host (keyboard/mouse/camera...), or the device waits to be control by the host (mobile phone as USB storage). Thus, the connected/disconnected event is useless in the most scenarios.