android 6.0 高通平臺sensor 工做機制及流程(原創)

最近工做上有碰到sensor的相關問題,正好分析下其流程做個筆記。java

這個筆記分三個部分:android

  1. sensor硬件和驅動的工做機制
  2. sensor 上層app如何使用
  3. 從驅動到上層app這中間的流程是如何

Sensor硬件和驅動的工做機制

先看看Accerometer +Gyro Sensor的原理圖:服務器

總結起來分四個部分(電源,地,通訊接口,中斷腳)。電源和地與平臺和芯片自己有關係,與咱們分析的沒有多少關係,根據sensor的特性保證sensor正常工做的上電時序。關於通訊接口,sensor與ap之間通訊通常有兩種接口(I2C/SPI)。因sensor數據量不大,I2C的速度足矣,目前使用I2C的居多。SDA是I2C的數據線,SCL是I2C的clock線。關於中斷腳就是INT。Sensor有兩個工做模式。一種是主動上報數據(每時每刻將獲取到的數據上報給系統),另個一種是中斷模式(當數據的變化大於了以前設置的觸發條件),好比手機翻轉大於45度,就會將當前的變化及當前數據上報給系統。數據結構

 

Sensor上層app的使用

先要註冊指定sensor的事件監聽,然在在有事件上報上來時,獲取上報的數據。app

具體代碼以下:dom

 1 SensorManager mSensorManager = (SensorManager)mContext.getSystemService(Context.SENSOR_SERVICE);
 2 Sensor mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 3 4 mSensorManager.registerListener(mSensorListener, mSensor, SensorManager.SENSOR_DELAY_GAME); 5 /* 6 public static final int SENSOR_DELAY_FASTEST = 0; 7 public static final int SENSOR_DELAY_GAME = 1; 8 public static final int SENSOR_DELAY_UI = 2; 9 public static final int SENSOR_DELAY_NORMAL = 3; 10 上報的速度能夠根據需求來選擇 11 */ 12 13 SensorEventListener mSensorListener = new SensorEventListener(){ 14 public void onAccuracyChanged(Sensor arg0, int arg1){ 15  } 16 17 public void onSensorChanged(SensorEvent event){ 18 if(event.sensor == null){ 19 return; 20  } 21 Log.d(TAG, "onSensorChanged"); 22 if(Sensor.TYPE_ACCELEROMETER == event.sensor.getType()) { 23 mGsensor = (float)event.values[SensorManager.DATA_Z]; 24 mSensorManager.unregisterListener(this); 25 Log.e(TAG, "mgsensor = " + mGsensor); 26 mOnSensorChangedFlag = false; 27  } 28  } 29 }

從驅動到上層App這中間的流程如何

前面二段分別說了驅動上報數據和app讀取數據,但中間的流程是如何的呢,這個是此篇博客的重點了。socket

驅動層上報數據後,HAL層怎麼處理呢?這個屬於input hal層的接收和分發了。來,咱們來啃啃這個骨頭:ide

frameworks/base/core/java/android/app/SystemServiceRegistry.java函數

419         registerService(Context.SENSOR_SERVICE, SensorManager.class,
420                 new CachedServiceFetcher<SensorManager>() {
421             @Override
422             public SensorManager createService(ContextImpl ctx) {
423                 return new SystemSensorManager(ctx.getOuterContext(),
424                   ctx.mMainThread.getHandler().getLooper());
425             }});
mContext.getSystemService(Context.SENSOR_SERVICE) 返回的就是SystemSensorManager 的對象(也是繼承SensorManager 類)。

frameworks/base/core/java/android/hardware/SensorManager.java
790     public Sensor getDefaultSensor(int type) {
......................................................................
841         List<Sensor> l = getSensorList(type);
842         boolean wakeUpSensor = false;

 846         if (type == Sensor.TYPE_PROXIMITY || type == Sensor.TYPE_SIGNIFICANT_MOTION ||
 847                 type == Sensor.TYPE_TILT_DETECTOR || type == Sensor.TYPE_WAKE_GESTURE ||
 848                 type == Sensor.TYPE_GLANCE_GESTURE || type == Sensor.TYPE_PICK_UP_GESTURE ||
 849                 type == Sensor.TYPE_WRIST_TILT_GESTURE) {
 850             wakeUpSensor = true;
 851         }
 852 //返回支持喚醒的sensor
 853         for (Sensor sensor : l) {
 854             if (sensor.isWakeUpSensor() == wakeUpSensor) return sensor;
 855         }
}

咱們再看看getSensorList這裏面有啥玩意。。。。oop

    public List<Sensor> getSensorList(int type) {
.......................................................................
        final List<Sensor> fullList = getFullSensorList();
        //而後再種全部sensor中找出對應的sensor
                    for (Sensor i : fullList) {
                        if (i.getType() == type)
                            list.add(i);
                    }
        return list;
}
getFullSensorList這個函數返回的是mFullSensorsList。
mFullSensorList是SystemSensorManager 遍歷全部的sensor獲得的集合。
下一步咱們再來看看registerListener是怎麼回事。
frameworks/base/core/java/android/hardware/SystemSensorManager.java
    protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor, int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags) {
        synchronized (mSensorListeners) {
            //先查看下此sensor的監聽隊列是否已經存在,若是不存在,就從新new個
            SensorEventQueue queue = mSensorListeners.get(listener);
            if (queue == null) {        
                queue = new SensorEventQueue(listener, looper, this, fullClassName);
                mSensorListeners.put(listener, queue);
                return true;
            } else {
                return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs);
            }
}

到這裏就明顯是一個消息隊列回調的問題了,確定是發現消息隊列裏有消息時就會回調具體的事件。咱們繼續擼代碼。

static final class SensorEventQueue extends BaseEventQueue {
        protected void dispatchSensorEvent(int handle, float[] values, int inAccuracy,
                long timestamp) {
......................................................
            // call onAccuracyChanged() only if the value changes
            final int accuracy = mSensorAccuracies.get(handle);
            if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
                mSensorAccuracies.put(handle, t.accuracy);
                mListener.onAccuracyChanged(t.sensor, t.accuracy);
            }
            mListener.onSensorChanged(t);
        }
}

從這裏就能夠看咱們listener裏實現的onAccuracyChanged,onSensorChanged是怎麼被調用。

frameworks/base/core/java/android/hardware/SensorEventListener.java

public interface SensorEventListener {
    public void onSensorChanged(SensorEvent event); public void onAccuracyChanged(Sensor sensor, int accuracy); }

就是一個接口,裏面聲明兩個函數。

看到回調是在dispatchSensorEvent裏作的,看看是誰調用的。。。

frameworks/base/core/jni/android_hardware_SensorManager.cpp

class Receiver : public LooperCallback {
    virtual int handleEvent(int fd, int events, void* data) {

        ASensorEvent buffer[16];
        while ((n = q->read(buffer, 16)) > 0) {
            for (int i=0 ; i<n ; i++) {
                if (buffer[i].type == SENSOR_TYPE_META_DATA) {
                    // This is a flush complete sensor event. Call dispatchFlushCompleteEvent
                    // method.
                    if (receiverObj.get()) {
                        env->CallVoidMethod(receiverObj.get(),
                                            gBaseEventQueueClassInfo.dispatchFlushCompleteEvent,
                                            buffer[i].meta_data.sensor);
                    }
                } else {
                    if (receiverObj.get()) {
                        env->CallVoidMethod(receiverObj.get(),
                                            gBaseEventQueueClassInfo.dispatchSensorEvent,
                                            buffer[i].sensor,
                                            mScratch,
                                            status,
                                            buffer[i].timestamp);
                    }
    
              }
            }
        }
}

讀到的數據,根據數據的類型去回調不一樣的接口。dispatchSensorEvent就是在這裏被調用的。

handleEvent這個是一個典型的eventQueue這事件處理,具體就不在這裏分析了。

 回調這些都有分析了,那事件是哪裏加入到消息隊列中的,那些消息又是怎麼來的呢,話說問題問對了,就能找到往下查的路了。。哈哈


理論這些確定會有sensor服務在開機的時候啓動的,那服務在哪裏,是怎麼啓動的呢。。。

frameworks/base/services/java/com/android/server/SystemServer.java

private void startBootstrapServices() {
...................................................
startSensorService();
}

這個startSensorService是個jni函數,調用的是:

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

static void android_server_SystemServer_startSensorService(JNIEnv* /* env */, jobject /* clazz */) {
//建立一個線程作sensorinit的工做
pthread_create( &sensor_init_thread, NULL, &sensorInit, NULL);
}

void* sensorInit(void *arg) {
    SensorService::instantiate();
}

sensorService服務就作初始化了,服務啓動時會作threadLoop(),

bool SensorService::threadLoop()
{
    ALOGD("nuSensorService thread starting...");

    const size_t minBufferSize = SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT;
    const size_t numEventMax = minBufferSize / (1 + mVirtualSensorList.size());

//device初始化
    SensorDevice& device(SensorDevice::getInstance());
    const size_t vcount = mVirtualSensorList.size();

    const int halVersion = device.getHalDeviceVersion();
    do {
//調用device.poll
        ssize_t count = device.poll(mSensorEventBuffer, numEventMax);
        if (count < 0) {
            ALOGE("sensor poll failed (%s)", strerror(-count));
            break;
        }
}

再看看SensorDevice 裏初始化和poll裏作了啥 :

SensorDevice::SensorDevice()
    :  mSensorDevice(0),
       mSensorModule(0)
{
  //get HAL module status_t err
= hw_get_module(SENSORS_HARDWARE_MODULE_ID, (hw_module_t const**)&mSensorModule); ALOGE_IF(err, "couldn't load %s module (%s)", SENSORS_HARDWARE_MODULE_ID, strerror(-err)); if (mSensorModule) {
     //open HAL module err
= sensors_open_1(&mSensorModule->common, &mSensorDevice); ................................................... }

SensorDevice初始化作了兩個動做,一個是獲取sensor HAL module,緊接着打開sensor hal module。

再一塊兒看年poll裏作啥了,

ssize_t SensorDevice::poll(sensors_event_t* buffer, size_t count) {
    if (!mSensorDevice) return NO_INIT;
    ssize_t c;
    do {
        c = mSensorDevice->poll(reinterpret_cast<struct sensors_poll_device_t *> (mSensorDevice),
                                buffer, count);
    } while (c == -EINTR);
    return c;
}

poll也是調用 的是Hal module裏的poll。

那sensor HAL裏作了啥呢,模塊作了啥呢?

sensor hal路徑:hardware/libhardware/modules/sensors/

 hardware/libhardware/modules/sensors/multihal.cpp

624 static int open_sensors(const struct hw_module_t* hw_module, const char* name,
625         struct hw_device_t** hw_device_out) {
626     ALOGV("open_sensors begin...");
627 //初始化加載高通的庫
628     lazy_init_modules();
629 
630     // Create proxy device, to return later.
631     sensors_poll_context_t *dev = new sensors_poll_context_t();
632     memset(dev, 0, sizeof(sensors_poll_device_1_t));
633     dev->proxy_device.common.tag = HARDWARE_DEVICE_TAG;
634     dev->proxy_device.common.version = SENSORS_DEVICE_API_VERSION_1_3;
635     dev->proxy_device.common.module = const_cast<hw_module_t*>(hw_module);
636     dev->proxy_device.common.close = device__close;
637     dev->proxy_device.activate = device__activate;
638     dev->proxy_device.setDelay = device__setDelay;
639     dev->proxy_device.poll = device__poll;
640     dev->proxy_device.batch = device__batch;
641     dev->proxy_device.flush = device__flush;
.......................................
}

咱們看看lazy_init_modules()這個,是把指定的的hal so加載起來。。

481 /*
482  * Ensures that the sub-module array is initialized.
483  * This can be first called from get_sensors_list or from open_sensors.
484  */
485 static void lazy_init_modules() {
486     pthread_mutex_lock(&init_modules_mutex);
487     if (sub_hw_modules != NULL) {
488         pthread_mutex_unlock(&init_modules_mutex);
489         return;
490     }
491     std::vector<std::string> *so_paths = new std::vector<std::string>();
481 /*
482  * Ensures that the sub-module array is initialized.
483  * This can be first called from get_sensors_list or from open_sensors.
484  */
485 static void lazy_init_modules() {
486     pthread_mutex_lock(&init_modules_mutex);
487     if (sub_hw_modules != NULL) {
488         pthread_mutex_unlock(&init_modules_mutex);
489         return;
490     }
491     std::vector<std::string> *so_paths = new std::vector<std::string>();
492     get_so_paths(so_paths);
493 
494     // dlopen the module files and cache their module symbols in sub_hw_modules
495     sub_hw_modules = new std::vector<hw_module_t *>();
496     dlerror(); // clear any old errors
497     const char* sym = HAL_MODULE_INFO_SYM_AS_STR;
498     for (std::vector<std::string>::iterator it = so_paths->begin(); it != so_paths->end(); it++) {
499         const char* path = it->c_str();
500         void* lib_handle = dlopen(path, RTLD_LAZY);
501         if (lib_handle == NULL) {
502             ALOGW("dlerror(): %s", dlerror());
503         } else {
504             ALOGI("Loaded library from %s", path);
505             ALOGV("Opening symbol \"%s\"", sym);
506             // clear old errors
507             dlerror();
508             struct hw_module_t* module = (hw_module_t*) dlsym(lib_handle, sym);
509             const char* error;
510             if ((error = dlerror()) != NULL) {
511                 ALOGW("Error calling dlsym: %s", error);
512             } else if (module == NULL) {
513                 ALOGW("module == NULL");
514             } else {
515                 ALOGV("Loaded symbols from \"%s\"", sym);
516                 sub_hw_modules->push_back(module);
517             }
518         }
519     }
520     pthread_mutex_unlock(&init_modules_mutex);
521 }
          //獲取的要加載so庫的路徑:/system/etc/sensors/hals.conf
492     get_so_paths(so_paths);
493 
494     // dlopen the module files and cache their module symbols in sub_hw_modules
495     sub_hw_modules = new std::vector<hw_module_t *>();
496     dlerror(); // clear any old errors
497     const char* sym = HAL_MODULE_INFO_SYM_AS_STR;
498     for (std::vector<std::string>::iterator it = so_paths->begin(); it != so_paths->end(); it++) {
499         const char* path = it->c_str();
500         void* lib_handle = dlopen(path, RTLD_LAZY);
501         if (lib_handle == NULL) {
502             ALOGW("dlerror(): %s", dlerror());
503         } else {
504             ALOGI("Loaded library from %s", path);
505             ALOGV("Opening symbol \"%s\"", sym);
506             // clear old errors
507             dlerror();
508             struct hw_module_t* module = (hw_module_t*) dlsym(lib_handle, sym);
509             const char* error;
510             if ((error = dlerror()) != NULL) {
511                 ALOGW("Error calling dlsym: %s", error);
512             } else if (module == NULL) {
513                 ALOGW("module == NULL");
514             } else {
515                 ALOGV("Loaded symbols from \"%s\"", sym);
516                 sub_hw_modules->push_back(module);
517             }
518         }
519     }
520     pthread_mutex_unlock(&init_modules_mutex);
521 }

這個路徑下就一個庫:sensors.ssc.so

再來看看poll看名字就能猜到是從數據隊列裏等數據,看代碼:

330 int sensors_poll_context_t::poll(sensors_event_t *data, int maxReads) {
331     ALOGV("poll");
332     int empties = 0;
333     int queueCount = 0;   
334     int eventsRead = 0;   
335    
336     pthread_mutex_lock(&queue_mutex);
337     queueCount = (int)this->queues.size();
338     while (eventsRead == 0) {
339         while (empties < queueCount && eventsRead < maxReads) {
340             SensorEventQueue* queue = this->queues.at(this->nextReadIndex);
341             sensors_event_t* event = queue->peek();

確實是消息隊列。。。

再來年看看加載的so庫這個是高通的sensor hal庫。

代碼路徑:vendor/qcom/proprietary/sensors/dsps/libhalsensors

 看先從哪裏插入數據的:

vendor/qcom/proprietary/sensors/dsps/libhalsensors/src/Utility.cpp

bool Utility::insertQueue(sensors_event_t const *data_ptr){
..........................
        if (q_head_ptr == NULL) {
        /* queue is empty */
            q_tail_ptr = q_ptr;
            q_head_ptr = q_ptr;
        } else {
        /* append to tail and update tail ptr */
            q_tail_ptr->next = q_ptr;
            q_tail_ptr = q_ptr;
        }
}

那看調用的有哪些呢?

Orientation.cpp (src): if (Utility::insertQueue(&la_sample)) {
PedestrianActivityMonitor.cpp (src): if (Utility::insertQueue(&sensor_data)) {
Pedometer.cpp (src): if (Utility::insertQueue(&la_sample)) {
PickUpGesture.cpp (src): if (Utility::insertQueue(&sensor_data)) {
QHeart.cpp (src): if (Utility::insertQueue(&la_sample)) {
RelativeMotionDetector.cpp (src): if (Utility::insertQueue(&sensor_data)) {
RotationVector.cpp (src): if (Utility::insertQueue(&la_sample)) {
Sensor.cpp (src): if (Utility::insertQueue(&flush_evt)){

....................................................................

都在各種sensor的processInd 這個函數中,每種sensor類型根據自身數據的特色,對其作數據結構作指定封裝。也就是所謂的工廠模式。

有一個調用比較特別:SMGRSensor.cpp 中processReportInd函數,這個函數中

void SMGRSensor::processReportInd(Sensor** mSensors, sns_smgr_periodic_report_ind_msg_v01* smgr_ind){
...............................
    handle = getHandleFromInd(smgr_ind->ReportId, smgr_data->DataType,
                     smgr_data->SensorId);
    if (handle == -1 ) {
        HAL_LOG_ERROR(" %s: ReportId = %d  DataType = %d SensorId = %d ", __FUNCTION__,
            smgr_ind->ReportId, smgr_data->DataType, smgr_data->SensorId);
        goto error;
    }
     /* Corresponds to screen orientation req, fill in the right type */
    if ((handle == HANDLE_ACCELERATION) && (smgr_ind->ReportId == HANDLE_MOTION_ACCEL)) {
        sensor_data.type = SENSOR_TYPE_SCREEN_ORIENTATION;
        sensor_data.sensor = HANDLE_MOTION_ACCEL;
    }

    if (mSensors[handle] != NULL) {
        (static_cast<SMGRSensor*>(mSensors[handle]))->processReportInd(smgr_ind, smgr_data, sensor_data);
    }
................................
    if (Utility::insertQueue(&sensor_data)) {
        Utility::signalInd(data_cb);
    }
}

這裏根據smgr_data->DataType又作了一次工廠模式的分發處理:

GyroscopeUncalibrated.cpp (src):  FUNCTION:  processReportInd
GyroscopeUncalibrated.cpp (src):void GyroscopeUncalibrated::processReportInd(
GyroscopeUncalibrated.cpp (src):    HAL_LOG_DEBUG("GyroscopeUncalibrated::processReportInd");
GyroscopeUncalibrated.h (inc):  FUNCTION:  processReportInd
GyroscopeUncalibrated.h (inc):    void processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
HallEffect.cpp (src):  FUNCTION:  processReportInd
HallEffect.cpp (src):void HallEffect::processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
HallEffect.h (inc):  FUNCTION:  processReportInd
HallEffect.h (inc):    void processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
Humidity.cpp (src):  FUNCTION:  processReportInd
Humidity.cpp (src):void Humidity::processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
Humidity.h (inc):  FUNCTION:  processReportInd
Humidity.h (inc):    void processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
IRGesture.cpp (src):  FUNCTION:  processReportInd
IRGesture.cpp (src):void IRGesture::processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
IRGesture.h (inc):  FUNCTION:  processReportInd
IRGesture.h (inc):    void processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
Light.cpp (src):  FUNCTION:  processReportInd
Light.cpp (src):void Light::processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
Light.h (inc):  FUNCTION:  processReportInd
Light.h (inc):    void processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
Magnetic.cpp (src):  FUNCTION:  processReportInd
Magnetic.cpp (src):void Magnetic::processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
Magnetic.h (inc):  FUNCTION:  processReportInd
Magnetic.h (inc):    void processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind
又是根據類型不一樣,作了另外一批類型sensor的處理。
繼續反向推導,
processReportInd其它這個也是processBufferingInd 調用的,processBufferingInd也是processInd 調用的。這就和其它的sensor到統一戰線上了。都是processInd處理的。
關鍵就是這processInd了,這個是一個回調SMGRSensor_sensor1_cb函數裏處理的。那這個回調是誰註冊,又是什麼調用的呢?
vendor/qcom/proprietary/sensors/dsps/libhalsensors/src/SensorsContext.cpp
這個裏面會在SensorContext實例化時註冊。
SensorsContext::SensorsContext()
    : active_sensors(0),
      is_accel_available(false),
      is_gyro_available(false),
      is_mag_available(false),
      is_prox_available(false),
      smgr_version(0)
{
。。。。。。。。。。。。。。。。。。。

    err = sensor1_open(&sensor_info_sensor1_cb->sensor1_handle, &context_sensor1_cb, (intptr_t)this);
。。。。。。。。。
}

那得去擼代碼啊,否則不知道啥時候回調context_sensor1_cb這個函數啊。。。

這個函數在另外一個庫中了libsensor1。。

這個函數作的事情比較多,分三部分:

sensor1_open( sensor1_handle_s **hndl,
              sensor1_notify_data_cb_t data_cbf,
              intptr_t cb_data )
{
.........................
  sensor1_init();
............................
sockfd = socket(AF_UNIX, SOCK_SEQPACKET, 0)) 
 strlcpy(address.sun_path, SENSOR_CTL_SOCKET, UNIX_PATH_MAX);
connect(sockfd, (struct sockaddr *)&address, len)
.....................................
libsensor_add_waiting_client(&cli_data);
 libsensor_add_client( &new_cli, false ) 
.....................................
}

那第一步先看看sensor1_init作了啥?

至關於建立了一個讀線程,一直在poll讀消息隊列裏的消息,讀到消息後,會封裝數據,而後發一個信息去喚醒另一個線程,喚醒的線程後面再說。

 

第二步再看看,建立了一個socket(一個客戶端socket),去鏈接服務端的socket(服務端的socket又是什麼東東),上個讀線程讀到的消息就是從socket讀到的消息(libsensor_read_socket),那必定是服務端socket發送過來的嘛。。。

第三步增長client,再看看這個又作了啥?

這裏又建立了一個回調線程,等有消息來時,喚醒本線程,而後回調sensor.ssc.庫裏的context_sensor1_cb。這個線程就誰喚醒的呢,哈哈,你們就能想到就是init中的那個讀線程嘛。

總結sensor1_open就是建立一個讀線程從socket客戶端中讀數據,讀到數據後,就回調sensor.ssc庫中的context_sensor1_cb,進而上報數據作進一步回調。

那問題來了,那個socket服務端又是怎麼回事呢。。。慢慢接近真相了。。。。


這時又出現了一個服務SensorDaemon:

代碼路徑:vendor/qcom/proprietary/sensors/dsps/sensordaemon

sns_main_setup 裏建立了socket服務器端,而後監聽客戶端socket的監聽,那何時往socket裏寫東西呢?

這就涉及另外一個回調函數了sns_main_notify_cb。這個回調函數則好就是sensor1_open裏註冊的。這個sensor1_open 與 libsensor1裏的sensor1_open不是同一個。

ok,那問題又來了,啥時候作的回調,和以前很相似,有一個讀線程,初始化後處理polling狀態,當收到消息時,就回調這個回調函數。

這個讀線程的數據是從哪裏來的呢,這就涉及到QMI service了。QMI service這部分代碼就不是AP這邊了,此份代碼就在modem的adsp代碼中了。

 找時間再來續modem這邊的adsp。 

相關文章
相關標籤/搜索