1. 程式人生 > android教程 > openDeviceLocked

openDeviceLocked

識別和匹配idc配置檔案

按鍵對映

status_t EventHub::openDeviceLocked(const char *devicePath) {
    char buffer[80];

    ALOGV("Opening device: %s", devicePath);

    int fd = open(devicePath, O_RDWR | O_CLOEXEC);/*open()系統呼叫返回檔案描述符,O_RDWR是指以讀寫方式開啟,O_CLOEXEC的作用是百度來的。Linux中,檔案描述符有一個屬性:CLOEXEC,即當呼叫exec()函式成功後,檔案描述符會自動關閉。在以往的核心版本(2.6.23以前)中,需要呼叫 fcntl(fd, F_SETFD, FD_CLOEXEC)來設定這個屬性。而新版本(2.6.23開始)中,可以在呼叫open函式的時候,通過 flags 引數設定CLOEXEC 功能,如open(filename, O_CLOEXEC)。*/
    if(fd < 0) {
        ALOGE("could not open %s, %s\n", devicePath, strerror(errno));
        return -1;
    }

  InputDeviceIdentifier identifier;//input裝置識別符號
  struct InputDeviceIdentifier {
      inline InputDeviceIdentifier() :
              bus(0), vendor(0), product(0), version(0) {
      }
  
      // Information provided by the kernel.
      String8 name;
      String8 location;
      String8 uniqueId;//唯一的ID
      uint16_t bus;
      uint16_t vendor;
      uint16_t product;
      uint16_t version;
  
      // A composite input device descriptor string that uniquely identifies the device
      // even across reboots or reconnections.  The value of this field is used by
      // upper layers of the input system to associate settings with individual devices.
      // It is hashed from whatever kernel provided information is available.
      // Ideally, the way this value is computed should not change between Android releases
      // because that would invalidate persistent settings that rely on it.
      String8 descriptor;
  };

    // Get device name.
  if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {//input_dev的name,”ft5x06”
        //fprintf(stderr, "could not get device name for %s, %s\n", devicePath, strerror(errno));
    } else {
        buffer[sizeof(buffer) - 1] = '\0';
        identifier.name.setTo(buffer);
    }

  // Check to see if the device is on our excluded list
  //檢查是不是要排除的device,一般都不是
    for (size_t i = 0; i < mExcludedDevices.size(); i++) {
        const String8& item = mExcludedDevices.itemAt(i);
        if (identifier.name == item) {
            ALOGI("ignoring event id %s driver %s\n", devicePath, item.string());
            close(fd);
            return -1;
        }
    }

    // Get device driver version.
    int driverVersion;
    if(ioctl(fd, EVIOCGVERSION, &driverVersion)) {// 得到EV_VERSION 0x010001
        ALOGE("could not get driver version for %s, %s\n", devicePath, strerror(errno));
        close(fd);
        return -1;
    }

    // Get device identifier.
    struct input_id inputId;
    if(ioctl(fd, EVIOCGID, &inputId)) {//返回struct input_id
        ALOGE("could not get device input id for %s, %s\n", devicePath, strerror(errno));
        close(fd);
        return -1;
    }
    identifier.bus = inputId.bustype;
    identifier.product = inputId.product;
    identifier.vendor = inputId.vendor;
    identifier.version = inputId.version;

    // Get device physical location.
    if(ioctl(fd, EVIOCGPHYS(sizeof(buffer) - 1), &buffer) < 1) {//物理位置,字串
        //fprintf(stderr, "could not get location for %s, %s\n", devicePath, strerror(errno));
    } else {
        buffer[sizeof(buffer) - 1] = '\0';
        identifier.location.setTo(buffer);
    }

    // Get device unique id.
    if(ioctl(fd, EVIOCGUNIQ(sizeof(buffer) - 1), &buffer) < 1) {//唯一ID,字串
        //fprintf(stderr, "could not get idstring for %s, %s\n", devicePath, strerror(errno));
    } else {
        buffer[sizeof(buffer) - 1] = '\0';
        identifier.uniqueId.setTo(buffer);
    }

    // Fill in the descriptor.
    setDescriptor(identifier);//設定identifier.descriptor

    // Make file descriptor non-blocking for use with poll().
    if (fcntl(fd, F_SETFL, O_NONBLOCK)) {//設定fd非阻塞
        ALOGE("Error %d making device file descriptor non-blocking.", errno);
        close(fd);
        return -1;
    }

    // Allocate device.  (The device object takes ownership of the fd at this point.)
    int32_t deviceId = mNextDeviceId++;//deviceId=1,mNextDeviceId=2
    Device* device = new Device(fd, deviceId, String8(devicePath), identifier);//一個input device

    ALOGV("add device %d: %s\n", deviceId, devicePath);
    ALOGV("  bus:        %04x\n"
         "  vendor      %04x\n"
         "  product     %04x\n"
         "  version     %04x\n",
        identifier.bus, identifier.vendor, identifier.product, identifier.version);
    ALOGV("  name:       \"%s\"\n", identifier.name.string());
    ALOGV("  location:   \"%s\"\n", identifier.location.string());
    ALOGV("  unique id:  \"%s\"\n", identifier.uniqueId.string());
    ALOGV("  descriptor: \"%s\"\n", identifier.descriptor.string());
    ALOGV("  driver:     v%d.%d.%d\n",
        driverVersion >> 16, (driverVersion >> 8) & 0xff, driverVersion & 0xff);

    // Load the configuration file for the device.
    loadConfigurationLocked(device);
  /*獲取輸入裝置配置檔案,有幾個路徑,依次找,找到為止
  // Figure out the kinds of events the device reports.
/*使用EVIOCGBIT ioctl可以獲取裝置的能力和特性。它告知你裝置是否有key或者button。EVIOCGBIT ioctl處理4個引數( ioctl(fd, EVIOCGBIT(ev_type, max_bytes), bitfield))。 ev_type是返回的 type feature( 0是個特殊 case,表示返回裝置支援的所有的 type features)。 max_bytes表示返回的最大位元組數。bitfield域是指向儲存結果的記憶體指標。return value表示儲存結果的實際位元組數,如果呼叫失敗,則返回負值。*/
    ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);
    ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(device->absBitmask)), device->absBitmask);
    ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);
    ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);
    ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);
    ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);
    ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);

    // See if this is a keyboard.  Ignore everything in the button range except for
  // joystick and gamepad buttons which are handled like keyboards for the most part.
  /*如果是鍵盤,除了操縱桿和大部分像鍵盤一樣處理的遊戲手柄按鈕之外,可以忽略所以的按鈕範圍*/
    bool haveKeyboardKeys = containsNonZeroByte(device->keyBitmask, 0, sizeof_bit_array(BTN_MISC))
            || containsNonZeroByte(device->keyBitmask, sizeof_bit_array(KEY_OK),
                    sizeof_bit_array(KEY_MAX + 1));
/*
haveKeyboardKeys:上報0~BTN_MISC(100)-1或KEY_OK(160)~KEY_MAX(0x2ff)之間的type。
haveGamepadButtons:上報BTN_MISC~BTN_MOUSE(0x110)-1或BTN_JOYSTICK(0x120)~BTN_DIGI(0x140)的type。
*/
    bool haveGamepadButtons = containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_MISC),
                    sizeof_bit_array(BTN_MOUSE))
            || containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_JOYSTICK),
                    sizeof_bit_array(BTN_DIGI));
    if (haveKeyboardKeys || haveGamepadButtons) {
        device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;/*input外設是一個鍵盤或者按鈕*/
    }

    // See if this is a cursor device such as a trackball or mouse.
    if (test_bit(BTN_MOUSE, device->keyBitmask)
            && test_bit(REL_X, device->relBitmask)
            && test_bit(REL_Y, device->relBitmask)) {
        device->classes |= INPUT_DEVICE_CLASS_CURSOR;//是一個游標,比如軌跡球或滑鼠
    }

    // See if this is a touch pad.
    // Is this a new modern multi-touch driver?
    if (test_bit(ABS_MT_POSITION_X, device->absBitmask)
            && test_bit(ABS_MT_POSITION_Y, device->absBitmask)) {
        // Some joysticks such as the PS3 controller report axes that conflict
        // with the ABS_MT range.  Try to confirm that the device really is
        // a touch screen.
        if (test_bit(BTN_TOUCH, device->keyBitmask) || !haveGamepadButtons) {
            device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT;
        }
    // Is this an old style single-touch driver?
    } else if (test_bit(BTN_TOUCH, device->keyBitmask)
            && test_bit(ABS_X, device->absBitmask)
            && test_bit(ABS_Y, device->absBitmask)) {
        device->classes |= INPUT_DEVICE_CLASS_TOUCH;
    }
/*
(1) 如果是一個touch pad(不透明的觸控板),還要看它是不是現代的多點觸控driver,所以要看一下有沒有report ABS_MT_POSITION_X和ABS_MT_POSITION_Y;多點協議要求的。
(2) 如果是多點上報協議,還要看下是不是操縱桿。比如PS3控制器也會上報座標軸,這與多點上報 ABS_MT範圍是衝突的,所以還要確認一下這個外設確實是觸控式螢幕。怎麼看呢?如果上報了BTN_TOUCH那就是touch,如果沒有上報BTN_TOUCH,也不是遊戲手柄按鈕,那也是touch。device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT。
(3) 如果是老式的單點上報,device->classes |= INPUT_DEVICE_CLASS_TOUCH。
*/
    // See if this device is a joystick.
    // Assumes that joysticks always have gamepad buttons in order to distinguish them
  // from other devices such as accelerometers that also have absolute axes.
  //如果這個外設是一個操縱桿。假設它總是有遊戲手柄按鈕,為了與同樣上報絕對座標的其他外設,比如感應器區分開來,還需要設定INPUT_DEVICE_CLASS_JOYSTICK。
    if (haveGamepadButtons) {
        uint32_t assumedClasses = device->classes | INPUT_DEVICE_CLASS_JOYSTICK;
        for (int i = 0; i <= ABS_MAX; i++) {
            if (test_bit(i, device->absBitmask)
                    && (getAbsAxisUsage(i, assumedClasses) & INPUT_DEVICE_CLASS_JOYSTICK)) {
                device->classes = assumedClasses;
                break;
            }
        }
    }

    // Check whether this device has switches.開關
    for (int i = 0; i <= SW_MAX; i++) {
        if (test_bit(i, device->swBitmask)) {
            device->classes |= INPUT_DEVICE_CLASS_SWITCH;
            break;
        }
    }

    // Check whether this device supports the vibrator.振盪器
    if (test_bit(FF_RUMBLE, device->ffBitmask)) {
        device->classes |= INPUT_DEVICE_CLASS_VIBRATOR;
    }

    // Configure virtual keys.
    if ((device->classes & INPUT_DEVICE_CLASS_TOUCH)) {
        // Load the virtual keys for the touch screen, if any.
        // We do this now so that we can make sure to load the keymap if necessary.
/*如果有的話,為觸控式螢幕load虛擬按鍵。我們現在這樣做,所以可以確保load鍵對映,如果需要的話。一般虛擬按鍵都是利用觸控式螢幕的邊緣座標模擬的按鍵。配置檔名就是/sys/board_properties/virtualkeys.{devicename},格式為:0x1:掃描碼:X:Y:W:H:0x1: ……例如:
0x01:158:55:835:90:55:0x01:139:172:835:125:55:0x01:102:298:835:115:55:0x01:217:412:835:95:55。如果定義了這個配置檔案就可以自動把RawInputEvent(原始輸入事件)轉換為KeyEvent(按鍵事件)。base/core/java/android/view/inputDevice.java負責處理虛擬按鍵。要實現虛擬按鍵還可以在driver中用input_event傳送按鍵訊息,往往是這種方式較為常用,尤其是需要校準的電阻屏。
注意:使用虛擬按鍵轉換成為的是按鍵的掃描碼,不是按鍵碼,因此依然需要經過按鍵佈局檔案的轉化才能得到按鍵碼。我們driver中所用的也是掃描碼,例如:KEY_MENU、KEY_BACK。
*/
        status_t status = loadVirtualKeyMapLocked(device);//load虛擬按鍵配置檔案
        if (!status) {
            device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;//支援鍵盤
        }
    }

    // Load the key map.
  // We need to do this for joysticks too because the key layout may specify axes.
  //Load按鍵對映,我們還需要為操縱桿做這個是因為鍵盤佈局可能是一個指定軸。
    status_t keyMapStatus = NAME_NOT_FOUND;
    if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) {
        // Load the keymap for the device.先找*.kl,再找*.kcm,查詢順序同
        keyMapStatus = loadKeyMapLocked(device);
    }

    // Configure the keyboard, gamepad or virtual keyboard.
    if (device->classes & INPUT_DEVICE_CLASS_KEYBOARD) {
        // Register the keyboard as a built-in keyboard if it is eligible.
//如果有資格註冊一個鍵盤作為嵌入鍵盤,什麼是有資格,就是if的條件了
        if (!keyMapStatus//上一節load keymap失敗了
                && mBuiltInKeyboardId == NO_BUILT_IN_KEYBOARD(建構函式是這樣初始化的)
                && isEligibleBuiltInKeyboard(device->identifier,
                        device->configuration, &device->keyMap)) {
            mBuiltInKeyboardId = device->id;
        }
/*isEligibleBuiltInKeyboard()成立的條件是:
(1) *.kcm有,type不是SPECIAL_FUNCTION。
(2) 如果idc檔案中設定了keyboard.builtIn = true,那(1)+(2)條件成立。
(3) 如果input device的name中含有"-keypad",那(1)+(3)條件也成立。 
*/
        // 'Q' key support = cheap test of whether this is an alpha-capable kbd
//簡單測試下是否有字母功能的鍵盤文字
        if (hasKeycodeLocked(device, AKEYCODE_Q)) {
            device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY;
        }

        // See if this device has a DPAD.//D-Pad( directional pad)方向鍵
        if (hasKeycodeLocked(device, AKEYCODE_DPAD_UP) &&
                hasKeycodeLocked(device, AKEYCODE_DPAD_DOWN) &&
                hasKeycodeLocked(device, AKEYCODE_DPAD_LEFT) &&
                hasKeycodeLocked(device, AKEYCODE_DPAD_RIGHT) &&
                hasKeycodeLocked(device, AKEYCODE_DPAD_CENTER)) {
            device->classes |= INPUT_DEVICE_CLASS_DPAD;
        }

        // See if this device has a gamepad.
        for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES)/sizeof(GAMEPAD_KEYCODES[0]); i++) {
            if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) {
                device->classes |= INPUT_DEVICE_CLASS_GAMEPAD;
                break;
            }
        }

        // Disable kernel key repeat since we handle it ourselves
//失能 kernel key repeat,因為我們除了它
        unsigned int repeatRate[] = {0,0};
        if (ioctl(fd, EVIOCSREP, repeatRate)) {
            ALOGW("Unable to disable kernel key repeat for %s: %s", devicePath, strerror(errno));
        }
    }

  // If the device isn't recognized as something we handle, don't monitor it.
  //如果device沒有被識別為我們可以處理的東西,就不要監視它了
    if (device->classes == 0) {
        ALOGV("Dropping device: id=%d, path='%s', name='%s'",
                deviceId, devicePath, device->identifier.name.string());
        delete device;
        return -1;
    }

  // Determine whether the device is external or internal.
  //確定是內部裝置還是外部裝置
    if (isExternalDeviceLocked(device)) {
        device->classes |= INPUT_DEVICE_CLASS_EXTERNAL;
    }
  (1) 如果idc配置檔案中,device.internal = true,就直接是內部裝置了。
  (2) 如果device.internal 沒有寫,要看input device的bus,如果是BUS_USB或者BUS_BLUETOOTH就是外部裝置。 
    // Register with epoll.
    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.u32 = deviceId;
    if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
        ALOGE("Could not add device fd to epoll instance.  errno=%d", errno);
        delete device;
        return -1;
    }
/*又添加了一個epoll事件,這次是要查詢/dev/input/eventx是否可讀。
*/
    // Enable wake-lock behavior on kernels that support it.
    // TODO: Only need this for devices that can really wake the system.
    bool usingSuspendBlockIoctl;
    char value[8];
    property_get("ro.platform.has.mbxuimode", value, "false");
    if(strcmp(value, "true") == 0) {
        usingSuspendBlockIoctl = !ioctl(fd, EVIOCSSUSPENDBLOCK, 0);//失能
    } else {
        usingSuspendBlockIoctl = !ioctl(fd, EVIOCSSUSPENDBLOCK, 1);//使能
    }
/*int property_get(const char *key, char *value, const char *default_value);
失能時,到kernel呼叫evdev_disable_suspend_block()->
  client->use_wake_lock = false;
  wake_lock_destroy(&client->wake_lock);
使能時呼叫evdev_enable_suspend_block()->
	wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, client->name);
	client->use_wake_lock = true;
	if (client->packet_head != client->tail)
這時候是上鎖,什麼時候解鎖呢?迴圈buffer首尾相接的時候。
	if (unlikely(client->head == client->tail)) {
		if (client->use_wake_lock)
			wake_unlock(&client->wake_lock);
  }
	if (client->use_wake_lock &&
		client->packet_head == client->tail)
		wake_unlock(&client->wake_lock);
*/
    // Tell the kernel that we want to use the monotonic clock for reporting timestamps
    // associated with input events.  This is important because the input system
    // uses the timestamps extensively and assumes they were recorded using the monotonic
    // clock.
/*通知kernel我們想用monotonic(單調遞增)時鐘作為input events的報告時間戳,這是非常重要的,假設input system用monotonic時鐘記錄時間戳,時間戳的應用非常廣泛。
    // In older kernel, before Linux 3.4, there was no way to tell the kernel which
    // clock to use to input event timestamps.  The standard kernel behavior was to
    // record a real time timestamp, which isn't what we want.  Android kernels therefore
    // contained a patch to the evdev_event() function in drivers/input/evdev.c to
    // replace the call to do_gettimeofday() with ktime_get_ts() to cause the monotonic
    // clock to be used instead of the real time clock.
/*在Linux 3.4之前的核心中,沒有辦法通知kernel用哪種時鐘作為input系統的時間戳。標準核心行為是記錄real(實時)時間的時間戳,這個時間並不是我們想要的。因此,android核心包含一個對drivers/input/evdev.c中evdev_event()函式的patch,用ktime_get_ts()取代 do_gettimeofday(),從而實現monotonic時鐘代替real time時鐘。
*/  
    // As of Linux 3.4, there is a new EVIOCSCLOCKID ioctl to set the desired clock.
    // Therefore, we no longer require the Android-specific kernel patch described above
  // as long as we make sure to set select the monotonic clock.  We do that here.
/*從Linux 3.4開始,出現了新的EVIOCSCLOCKID EVIOCSCLOCKID來設定期望的時鐘。因此,我們不再需要android特殊的核心patch,綜上所述,只有我們確定需要設定 monotonic clock,就執行下列程式碼。
*/
    int clockId = CLOCK_MONOTONIC;
    bool usingClockIoctl = !ioctl(fd, EVIOCSCLOCKID, &clockId);
    ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=0x%x, "
            "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, "
            "usingSuspendBlockIoctl=%s, usingClockIoctl=%s",
         deviceId, fd, devicePath, device->identifier.name.string(),
         device->classes,
         device->configurationFile.string(),
         device->keyMap.keyLayoutFile.string(),
         device->keyMap.keyCharacterMapFile.string(),
         toString(mBuiltInKeyboardId == deviceId),
         toString(usingSuspendBlockIoctl), toString(usingClockIoctl));

    addDeviceLocked(device);
    return 0;
}
void EventHub::addDeviceLocked(Device* device) {
    mDevices.add(device->id, device);
    device->next = mOpeningDevices;
    mOpeningDevices = device;
}
//KeyedVector.add()新增一個鍵值對,最後通過device->id就能找到device。
//通過mOpeningDevices可以找到我們第一open的裝置,一直next下去,所以open的都找到了。