linux kernel switch driver(android headset device detection)


 

 

總結下linux kernel switch driver。

這里的switch driver是為監聽CPU gpio口狀態變化的,switch可以理解為gpio口狀態變化。

switch driver是因android引入的。

 

總結地說,就是在switch driver中創建kernel thread周期性地檢測指定的gpio口的狀態,如果檢測到gpio口狀態變化了,就發uevent;android native層的input flinger會去讀這個event,讀到后往android java層notify,notify給InputManagerService/WiredAccessoryManager,WiredAccessoryManager在處理這個msg,比如當收到的msg表示有耳機接入時,會將外放mute掉。

 

下面以耳機接入為例具體說下androd系統中耳機接入檢測的過程

kernel/arch/arm64/boot/dts/xxx.dts

switch_gpio {
        compatible = "mstar,switch-gpio";
        switch-name = "h2w";
        switch-gpio = <66>;
        switch-inverse = <0>;
    };

 

上面的switch-gpio是想要監聽的CPU gpio index,用來告訴switch driver要監聽的gpio口

 

解析上述dts switch_gpio section是在kernel switch driver中:

drivers/switch/switch_gpio.c

static int gpio_switch_probe(struct platform_device *pdev)
{
    struct gpio_switch_platform_data *pdata = pdev->dev.platform_data;
    struct gpio_switch_data *switch_data;
    int ret = 0;

    if (!pdata)
        return -EBUSY;

    switch_data = kzalloc(sizeof(struct gpio_switch_data), GFP_KERNEL);
    if (!switch_data)
        return -ENOMEM;

    switch_data->sdev.name = pdata->name;
    switch_data->gpio = pdata->gpio;
    switch_data->name_on = pdata->name_on;
    switch_data->name_off = pdata->name_off;
    switch_data->state_on = pdata->state_on;
    switch_data->state_off = pdata->state_off;
    switch_data->sdev.print_state = switch_gpio_print_state;

    ret = switch_dev_register(&switch_data->sdev);

在kernel switch driver中會創建switch class,對應路徑為/sys/class/switch。還會根據dts文件中指定的name創建device,例如上面dts文件中的h2w,所以對應路徑是/sys/class/switch/h2w。

在switch driver中,會創建kernel thread來定期檢測指定的gpio的狀態,然后判斷gpio口狀態是否改變,如果發現gpio口狀態變化,會發uevent:

drivers/switch/switch_class.c

void switch_set_state(struct switch_dev *sdev, int state)
{
    char name_buf[120];
    char state_buf[120];
    char *prop_buf;
    char *envp[3];
    int env_offset = 0;
    int length;

    if (sdev->state != state) {
        sdev->state = state;

        prop_buf = (char *)get_zeroed_page(GFP_KERNEL);
        if (prop_buf) {
            length = name_show(sdev->dev, NULL, prop_buf);
            if (length > 0) {
                if (prop_buf[length - 1] == '\n')
                    prop_buf[length - 1] = 0;
                snprintf(name_buf, sizeof(name_buf),
                    "SWITCH_NAME=%s", prop_buf);
                envp[env_offset++] = name_buf;
            }
            length = state_show(sdev->dev, NULL, prop_buf);
            if (length > 0) {
                if (prop_buf[length - 1] == '\n')
                    prop_buf[length - 1] = 0;
                snprintf(state_buf, sizeof(state_buf),
                    "SWITCH_STATE=%s", prop_buf);
                envp[env_offset++] = state_buf;
            }
            envp[env_offset] = NULL;
            kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp);
            free_page((unsigned long)prop_buf);
        } else {
            printk(KERN_ERR "out of memory in switch_set_state\n");
            kobject_uevent(&sdev->dev->kobj, KOBJ_CHANGE);
        }
    }
}
EXPORT_SYMBOL_GPL(switch_set_state);

 

 

在switch driver中也可以選擇注冊gpio的kernel irq來實現對指定gpio口狀態變化的監聽

 

在switch driver中發了uevent后,上層會來讀這個event,就是由android中的input flinger來讀:

frameworks/native/services/inputflinger/InputReader.cpp

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    Vector<InputDeviceInfo> inputDevices;
    { // acquire lock
        AutoMutex _l(mLock);

        oldGeneration = mGeneration;
        timeoutMillis = -1;

        uint32_t changes = mConfigurationChangesToRefresh;
        if (changes) {
            mConfigurationChangesToRefresh = 0;
            timeoutMillis = 0;
            refreshConfigurationLocked(changes);
        } else if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
        }
    } // release lock

    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast();

        if (count) {
            processEventsLocked(mEventBuffer, count);
        }  

上面的getEvents后,接下來就是處理這個event了,調用processEventsLocked()這個函數會call到SwitchInputMapper的procesSwitch/sync函數,sync函數是將這個event插入event queue

void SwitchInputMapper::processSwitch(int32_t switchCode, int32_t switchValue) {
    if (switchCode >= 0 && switchCode < 32) {
        if (switchValue) {
            mSwitchValues |= 1 << switchCode;
        } else {
            mSwitchValues &= ~(1 << switchCode);
        }
        mUpdatedSwitchMask |= 1 << switchCode;
    }
}

void SwitchInputMapper::sync(nsecs_t when) {
    if (mUpdatedSwitchMask) {
        uint32_t updatedSwitchValues = mSwitchValues & mUpdatedSwitchMask;
        NotifySwitchArgs args(when, 0, updatedSwitchValues, mUpdatedSwitchMask);
        getListener()->notifySwitch(&args);

        mUpdatedSwitchMask = 0;
    }
}

 

接下來是處理event queue中的event了,如下的flush()函數:

frameworks/native/services/inputflinger/InputListener.cpp

void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    for (size_t i = 0; i < count; i++) {
        NotifyArgs* args = mArgsQueue[i];
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear();
}

 

對於switch而言,就是call如下的notify函數:

void NotifySwitchArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifySwitch(this);
}

 

上述的listener是InputDispatcher,所以會call到InputDispatcher::notifySwitch()

frameworks/native/services/inputflinger/InputDispatcher.cpp

void InputDispatcher::notifySwitch(const NotifySwitchArgs* args) {
#if DEBUG_INBOUND_EVENT_DETAILS
    ALOGD("notifySwitch - eventTime=%" PRId64 ", policyFlags=0x%x, switchValues=0x%08x, "
            "switchMask=0x%08x",
            args->eventTime, args->policyFlags, args->switchValues, args->switchMask);
#endif

    uint32_t policyFlags = args->policyFlags;
    policyFlags |= POLICY_FLAG_TRUSTED;
    mPolicy->notifySwitch(args->eventTime,
            args->switchValues, args->switchMask, policyFlags);
}

 

上面的notifySwitch()是NativeInputManager::notifySwitch()

frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

void NativeInputManager::notifySwitch(nsecs_t when,
        uint32_t switchValues, uint32_t switchMask, uint32_t /* policyFlags */) {
#if DEBUG_INPUT_DISPATCHER_POLICY
    ALOGD("notifySwitch - when=%lld, switchValues=0x%08x, switchMask=0x%08x, policyFlags=0x%x",
            when, switchValues, switchMask, policyFlags);
#endif
    ATRACE_CALL();

    JNIEnv* env = jniEnv();

    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifySwitch,
            when, switchValues, switchMask);
    checkAndClearExceptionFromCallback(env, "notifySwitch");
}

 

 

上面的CallVoidMethod調用就是native層call java層的函數,call的對應的java層的函數是InputManagerService類中的方法

frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

    // Native callback.
    private void notifySwitch(long whenNanos, int switchValues, int switchMask) {
        if (DEBUG) {
            Slog.d(TAG, "notifySwitch: values=" + Integer.toHexString(switchValues)
                    + ", mask=" + Integer.toHexString(switchMask));
        }

        if ((switchMask & SW_LID_BIT) != 0) {
            final boolean lidOpen = ((switchValues & SW_LID_BIT) == 0);
            mWindowManagerCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen);
        }

        if ((switchMask & SW_CAMERA_LENS_COVER_BIT) != 0) {
            final boolean lensCovered = ((switchValues & SW_CAMERA_LENS_COVER_BIT) != 0);
            mWindowManagerCallbacks.notifyCameraLensCoverSwitchChanged(whenNanos, lensCovered);
        }

        if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) {
            mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues,
                    switchMask);
        }

 

對於此次的headset問題,就是走的SW_JACK_BITS case,所以是call mWiredAccessoryCallbacks.notifyWiredAccessoryChanged,notifyWiredAccessoryChanged對應是WiredAccessoryManager中的方法:

frameworks/base/services/core/java/com/android/server/WiredAccessoryManager.java

    public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask) {
        if (LOG) Slog.v(TAG, "notifyWiredAccessoryChanged: when=" + whenNanos
                + " bits=" + switchCodeToString(switchValues, switchMask)
                + " mask=" + Integer.toHexString(switchMask));

        synchronized (mLock) {
            int headset;
            mSwitchValues = (mSwitchValues & ~switchMask) | switchValues;
            switch (mSwitchValues &
                (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT)) {
                case 0:
                    headset = 0;
                    break;

                case SW_HEADPHONE_INSERT_BIT:
                    headset = BIT_HEADSET_NO_MIC;
                    break;

                case SW_LINEOUT_INSERT_BIT:
                    headset = BIT_LINEOUT;
                    break;

                case SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT:
                    headset = BIT_HEADSET;
                    break;

                case SW_MICROPHONE_INSERT_BIT:
                    headset = BIT_HEADSET;
                    break;

                default:
                    headset = 0;
                    break;
            }

            updateLocked(NAME_H2W,
                (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) | headset);
        }
    }

 

 

    private void updateLocked(String newName, int newState) {
        // Retain only relevant bits
        int headsetState = newState & SUPPORTED_HEADSETS;
        int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG;
        int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL;
        int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT);
        boolean h2wStateChange = true;
        boolean usbStateChange = true;
        if (LOG) Slog.v(TAG, "newName=" + newName
                + " newState=" + newState
                + " headsetState=" + headsetState
                + " prev headsetState=" + mHeadsetState);

 

所以,如果是檢測到有耳機設備接入,會有如下的log。如果headsetState為2,表示有不帶mic的耳機接入了:

??[15-51-04.505]11-30 02:14:21.985  3262  3262 V WiredAccessoryManager: newName=h2w newState=2 headsetState=2 prev headsetState=0

 

 

    private static final int BIT_HEADSET = (1 << 0);
    private static final int BIT_HEADSET_NO_MIC = (1 << 1);
    private static final int BIT_USB_HEADSET_ANLG = (1 << 2);
    private static final int BIT_USB_HEADSET_DGTL = (1 << 3);
    private static final int BIT_HDMI_AUDIO = (1 << 4);
    private static final int BIT_LINEOUT = (1 << 5);
    private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC|
                                                   BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL|
                                                   BIT_HDMI_AUDIO|BIT_LINEOUT);

 

 

可以執行如下的命令查看系統中目前接入的耳機類設備,例如cat到的值為2表示當前系統有接上不帶mic的耳機:

 cat /sys/class/switch/h2w/state

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

??[15-51-04.505]11-30 02:14:21.985  3262  3262 V WiredAccessoryManager: newName=h2w newState=2 headsetState=2 prev headsetState=0

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM