Android Sensor Service Porting

Android의 linux kernel device driver와 Android OS 가 어떻게 만나는지, 어떻게 연결해야 하는지 알아보자


Sensor Stack


출처 : https://source.android.com/devices/sensors/index.html
가운데 HAL 에 포함된 sensor.h와 sensors.cpp가 핵심이다


Data Structure

hardware/libhardware/include/hardware/sensors.h

/**
 * Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
 * and the fields of ths data structure must begin with hw_module_t
 * followed by module specific information
 **/
struct sensors_module_t {
    struct hw_module_t common;
    int (*get_sensors_list)(struct sensors_module_t* module, 
            struct sensor_t const** list);
}

주석에 나와있듯 모든 하드웨어 모듈은 반드시 이 구조체 형식으로 작성되어야 한다
얘가 젤로 중요. 이 형식의 HAL_MODULE_INFO_SYM (HMI)를 선언하는 것으로 시작된다


hardware/libhardware/include/hardware/hardware.h

typedef struct hw_module_t {
    uint32_t tag;
    uint16_t module_api_version;
    uint16_t hal_api_version;
    const char *id;
    const char *name;
    const char *author;
    struct hw_module_methods_t* methods;
    void* dso;
    ...
}hw_module_t;

typedef struct hw_module_methods_t {
    int (*open)(const struct hw_module_t* module, const char* id, 
        struct hw_device_t** device);
}hw_module_methods_t;

typedef struct hw_device_t {
    uint32_t tag;
    uint32_t version;
    struct hw_module_t* module;
    ...
    int (*close)(struct hw_device_t* device);
}hw_device_t;

일단 이런 형식이 있다는 것만 알자
바로 아래 코드에서 이 자료형을 사용해 구조체가 구현된다


Sensors.cpp - Java Framework, HAL

C++ 영역에서 HAL을 어떻게 접근할지, JAVA framework와 어떻게 연결될지 가장 중요한 코드라 할 수 있다
예를 들어 light, proximity sensor의 코드를 보자
device/lge/hammerhead/libsensors/sensors.cpp

static struct sensor_t sSensorList[GLOBAL_SENSORS + LOCAL_SENSORS] = { 
    {   
        .name       = "Light Sensor",
        .vendor     = "Avago Technologies",
        .version    = 1,
        .handle     = SENSORS_LIGHT_HANDLE,
        .type       = SENSOR_TYPE_LIGHT,
        ...
    },  
    {   
        .name       = "Proximity Sensor",
        .vendor     = "Avago Technologies",
        .version    = 1,
        .handle     = SENSORS_PROXIMITY_HANDLE,
        .type       = SENSOR_TYPE_PROXIMITY,
        ...
    },  
};

struct sensors_module_t HAL_MODULE_INFO_SYM = {     //HMI 기억 납니까
    common: {
        tag: HARDWARE_MODULE_TAG,
        version_major: 1,
        version_minor: 0,
        id: SENSORS_HARDWARE_MODULE_ID,
        name: "LGE Sensor module",
        author: "LG Electronics Inc.",
        methods: &sensors_module_methods,    //얘 까먹지 말고 있어봐
        dso: NULL,
        reserved: {0} 
    },  
    get_sensors_list: sensors__get_sensors_list,
};

이 파일은 각기 다양한 센서의 hub 정도 된다
보통 각 vendor는 자사의 다양한 칩을 이런식으로 sensors.cpp 에 적당히 선언해놓고 객체를 생성해서 관리하는 식으로 만든다
각 센서의 세부적인 객체 모양이나 자료형이 다르기 때문에 따로 파일을 분류해서 구현하고 그 들을 sensors.cpp에서 통합해 관리하는 식이다
sensors.cpp는 윗쪽 형님들에게 불려 java framework까지 올라가게 되는데 그 부분은 일단 나중에 보고 먼저 밑으로 내려가자


HAL에서 device driver와 어떻게 통신?

위에 선언 된 HAL_MODULE_INFO_SYM 으로 sensors_motule_methods의 open method 가 불리면
sensors_poll_context_t object가 생성되고 얘는 open method의 마지막 인자에 실려 나간다

static struct hw_module_methods_t sensors_module_methods = {    //제가 아까 걥니다
    open : open_sensors
};

static int open_sensors(const struct hw_module_t* module, const char* id
, struct hw_device_t** device)
{
    sensors_poll_context_t *dev = new sensors_poll_context_t();    //각각의 센서들 객체 생성
    dev->device.common.module    = const_cast<hw_module_t*>(module);
    dev->device.common.close     = poll_close;
    ...
    dev->device.poll             = poll__poll;

    *device = &dev->device.common;        //sensors_open_1 에게 호출 됩니다. 매우 중요
}

sensors_poll_context_t::sensors_poll_context_t() {
    ...
    mSensor[light] = new LightSensor();        //이런식으로 각 센서별 객체를 생성한다
    mPollFds[light].fd = mSensor[light]->getFd();    //그리고 이렇게 poll 관련 설정을 한다
    mPollFds[light].events = POLLIN;
    mPollFds[light].revents = 0;

    mSensor[proximity] = new ProximitySensor();
    ...    
}

이 코드의 흐름이 일반적인 기법으로 보인다
open_sensors (복수형이다!)를 호출할 때 센서 별 각 object들을 쫙 만들고
각각 이벤트 감지용 poll 설정을 하고
pipe로 활성화 메세지가 왔을 때 poll 이벤트를 감지해서 읽는다

*device = &dev->device.common;

이 코드가 중요한데 open의 인자 struct hw_device_t** device로 사용할 수도 있고
sensors_poll_device_1_t 형식으로 사용할 수도 있다. 구조체의 가장 최상단 멤버 주소를 활용한 트릭
&dev->device.common, &dev->device 이 두 주소는 같다
이 부분에 관해서는 너무 길어지니 이 링크를 참조하자

참고 #1 : https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html
참고 #2 :


각 센서별 object는 이런식이다
device/lge/hammerhead/libsensors/LightSensor.cpp

#define I2C "/sys/bus/i2c/devices/3-0039"
int LightSensor::enable(int32_t handle, int en)
{
    ...
    strcpy(sysfs, I2C);
    strcat(sysfs, "enable_als_sensor");

    int open(sysfs, O_RDWR);
    ...
    write(fd, ...);
}

sysfs 열어서 write하는 것은 Proximity Sensor 같은 다른 코드 들도 동일
device driver 레벨에서는 하드웨어와 통신하며 sysfs 인터페이스를 제공한다
혹은 이런 예제도 있다 google pixel이 포함된 최신 android source 기준
device/htc/flounder/sensor_hub/linsensors/sensors.cpp
device/htc/flounder/sensor_hub/linsensors/CwMcuSensor.cpp
혹은
device/google/dragon/sensor_hub/sensors.cpp
device/google/dragon/sensor_hub/cros_ec_sensors.cpp


poll은 어떻게 이벤트 읽어오나요

아까 open_sensors에 등록 되었던 poll__poll은 pollEvents를 호출한다

int sensors_poll_context_t::pollEvents(sensors_event_t *data, int count)
{
    do {
        for(등록된 센서수) {
            if(mPollFds[i].revents & (POLLIN | POLLPRI)) {    //남아있는 poll 이벤트가 있는지 확인
                ...
                nb = sensor->readEvents(data, count);        //있다면 이벤트를 읽어온다
                if(nb < count) {
                    // no more data for this sensor
                    mPollFds[i].revents = 0;
                }
                count -= nb;
                nbEvents += nb;
                data += nb;
            }

           if (count) {        //요청한 수량 만큼 아직 이벤트를 못 읽어왔다면 다시 poll 체크
                do {                                                    //-1 -> no timeout, 0 -> return immediately
                    n = poll(mPollFds, numFds, nbEvents ? 0 : -1);        //그동안 받은 데이터가 없다면 무한 기다림
                } while (n < 0 && errno == EINTR);
                if (n < 0) {
                    ALOGE("poll() failed (%s)", strerror(errno));
                    return -errno;
                }   
                if (mPollFds[wake].revents & (POLLIN | POLLPRI)) {        //activate method를 통해 pipe로 wake up! 메시지
                    char msg;
                    int result = read(mPollFds[wake].fd, &msg, 1); 
                    ALOGE_IF(result < 0, "error reading from wake pipe (%s)", strerror(errno));
                    ALOGE_IF(msg != WAKE_MESSAGE, "unknown message on wake queue (0x%02x)", int(msg));
                    mPollFds[wake].revents = 0;
                }이거 왜 있는 코드인지 모르겠다
            }  
        }
    }while(n && count);

    return nbEvents;
}

poll이 데이터가 들어오는지 보고 있다가 들어오면 readEvents를 호출하는데 얘는 각 센서의 구현 파일에 정의되어 있다
참고 : http://www.joinc.co.kr/w/Site/Network_Programing/Documents/Poll

예를 들면 device/lge/hammerhead/libsensors/LightSensor.cpp

int LightSensor::readEvents(sensors_event_t* data, int count)
{
    ...
    ssize_t n = mInputReader.fill(data_fd);                //큐에 데이터 넣어달라고 요청
    input_event const* event;

    while(count && mInputReader.readEvent(&event)) {    //data가 담긴 포인터를 받아온다
        event를 가지고 처리
    }
}

놀랍게도 또! 객체를 호출해서 데이터가 들어왔나 확인을 요청하고 읽어온다
LightSensor중에도 invensense의 여러 모델이 있어서 이렇게 객체를 다시 생성해서 나누어놓았다

hardware/invensense/60xx/libsensors_iio/InputEventReader.cpp

ssize_t InputEventCircularReader::fill(int fd)
{
    ...
    const ssize_t nread = read(fd, mHead, mFreeSpace * sizeof(input_event));
    ...
    numEvnetsRead = nread / sizeof(input_event);
    ...
    return numEventsRead;
}

이 내부의 구현은 보통 ring buffer를 통해 데이터를 읽는 식으로 구현되어 있다
상세하게 들어가지는 않으려 한다
대충 /dev/iio:device 같은 장치를 open 해서 ring buffer에 값을 쌓아주는 식으로 비슷하게 구현되어 있다

sysfs 쪽은 센서의 하드웨어적인 제어만 하고 dev 쪽은 데이터 r/w에 관여하는 것 같다

참고 코드
device/htc/flounder/sensor_hub/linsensor/InputEventReader.cpp
device/htc/flounder/sensor_hub/libsensors/CwMcuSensor.cpp
open 후 read를 호출하면 device driver에서 특정 자료형 형태로 데이터를 보낸다


정리

만약 lge나 samsung 같은 대형 벤더에 속해 센서 서비스를 안드로이드에 제공할 수 있도록 포팅해야 한다면 sensors.cpp 만 개발한다고 되는 것이 아니라 device/vendor name/device name/ 에 있는 Android.mk 같은 수많은 mk파일들과 쉘스크립트 등등 파일들을 같이 세팅해야 할 것이고(물론 거기서 일하는 분들이 여기서 이걸 보고 있지는 않겠다만)
그게 아니라 일부 센서만 포팅해야 하는 경우라면 이미 구현되어 있는 복잡한 sensors.cpp 및 기타 환경 설정을 그대로 사용하고 거기에 추가로 자신이 포팅하고자 하는 센서 코드만 개발해서 추가하면 될 것 같다

예를 들면 device/htc/flounder/sensor_hub/libsensors/ 디렉토리를 보면
Android.mk 파일이 있는데 우리가 그간 봐왔던 반가운 makefile 스타일의 환경 설정이 있다

...
LOCAL_MODULE := sensors.$(TARGET_BOOTLOADER_BOARD_NAME)
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_OWNER := htc 
LOCAL_SRC_FILES :=                  \   
                   sensors.cpp      \   
                   SensorBase.cpp   \   
                   CwMcuSensor.cpp  \
                   InputEventReader.cpp
LOCAL_SHARED_LIBRARIES := liblog libcutils libdl
...

이런식으로 여기에 적당한 센서 객체를 구현해서 집어 넣으면 될 것 같다
예를 들면 DetectHRM.cpp 심박 감지 센서 객체를 구현하고
sensors.cpp에서 이 객체를 생성하는 스타일로 만들면 될 것 이다(아마도)

이제 다음페이지에서 sensors.cpp에서 윗쪽으로 어떻게 연결되는지 살펴보자!!


기타 잡코드 예제

hardware/qcom/display/msm8226/liblight/lights.c
device driver가 sysfs의 class에 각종 인터페이스를 등록, 접근 제어할 수 있도록 함
android framework에서는 sysfs를 open 해서 write하는 식으로 제어

char const*const RED_LED_FILE = "/sys/class/leds/red/brightness";
...

다양한 command는 hw_module_methods_t의 id 인자를 통해 구분


Posted by 쵸코케키

블로그 이미지
chocokeki
쵸코케키

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.4
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

최근에 올라온 글

최근에 달린 댓글

글 보관함