Sensor Stack


출처 : https://source.android.com/devices/sensors/index.html
이번에는 HAL에서 framework로 어떻게 올라가는지 확인해보려 한다

framework에서 HAL을 어떻게 호출?

이 파일에는 볼게 많다. 실제 휴대폰에 adb로 들어가서 /system/lib/hw 디렉토리에 뭐가 있나 봐보자
hardware/libhardware/hardware.c

int hw_get_module_by_class(const char *class_id, const char *inst,
                            const struct hw_module_t **module)
{
    ...
    return load(classid_, path, module);
}

int hw_get_module(const char *id, const struct hw_module_t **module)
{
    return hw_get_module_by_class(id, NULL, module);
}

#define HAL_MODULE_INFO_SYM                 HMI
#define HAL_MODULE_INFO_SYM_AS_STR         "HMI"
static int load(const char *id,
        const char *path,
        const struct hw_module_t **pHmi)
{
    ...
    void *handle = dlopen(path, RTLD_NOW);

    /* Get the address of the struct hal_module_info */
    const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
    struct hw_module_t *hmi = (struct hw_module_t *)dlsym(handle, sym);
    if(hmi->dso)
        hmi->dso = handle;
    *pHmi = hmi;
    ...
}

네 그렇습니다. load라는 녀석을 통해 심볼 호출이 되어 HMI가 읽힙니다
hw_get_module_by_class 같은 함수는 밑의 SensorDevice.cpp에서 호출이 됩니다


hardware/libhardware/include/hardware/sensors.h

static inline int sensors_open_1(const struct hw_module_t* module,
        sensors_poll_device_1_t** device) {
    return module->methods->open(module,
            SENSORS_HARDWARE_POLL, (struct hw_device_t**)device);
}

static inline int sensors_close_1(sensors_poll_device_1_t* device) {
    return device->common.close(&device->common);
}

아주 오래전에 open method가 호출 된다고 했던거 기억하는지요
이 함수는 아래의 class에서 호출 됩니다
잘 기억이 나지 않는다면 1편을 참조하시기 바랍니다



frameworks/native/services/sensorservice/SensorDevice.cpp

SensorDevice::SensorDevice()
    :    mSensorDevice(0),
        mSensorModule(0)
{
    status_t err = hw_get_module(SENSORS_HARDWARE_MODULE_ID,
            (hw_module_t const**)&mSensorModule);            //HMI에서 정의 했던 SENSORS_HARDWARE_MODULE_ID를 여기서 호출

    err = sensors_open_1(&mSensorModule->common, &mSensorDevice);    //HMI open method call & get mSensorDevice
    if (mSensorDevice) {
        sensor_t const* list;
        ssize_t count = mSensorModule->get_sensors_list(mSensorModule, &list);    //HMI call get_sensors_list
        mActivationCount.setCapacity(count);
        Info model;
        for (size_t i=0 ; i<size_t(count) ; i++) {
            mActivationCount.add(list[i].handle, model);
            mSensorDevice->activate(        //아까 &dev->device.common으로 보냈던거 구조체 형변환 기억하나요?
                    reinterpret_cast<struct sensors_poll_device_t *>(mSensorDevice),
                    list[i].handle, 0); 
        }
}

activate method 처럼 HMI에서 정의한 함수들을 호출한다
activate 호출이 되면 pipe로 wake message가 날아간다 그리고 poll에서 pipe를 확인하고 이벤트를 읽는다

기타 HMI에서 정의 되었던 각종 method들 batch, poll, setDelay 등 모두 SensorDevice.cpp 에서 호출한다
SensorDevice.cpp는 google에서 만드니 우리가 건들 부분은 없고 포팅을 위해서는 Sensors.cpp 이하만 구현 해주면 될 것이다
애초에 디렉토리 이름이 frameworks/native/serivces/services/ 이다

그 이후로 java랑 어떻게 붙냐면 무지막지하게 긴데 그걸 다 묘사할 수도 없고 대충 써보면
SensorService.cpp -> SensorEventQueue.cpp -> android_hardware_SensorManager.cpp -> SystemSensorManager.java


JNI

java와 c++의 교두보 같은 느낌이다
모든 jni 들은 android_hardware_xxxx.cpp 이런식으로 파일명이 붙는다

framework/base/core/jni/android_hardware_SensorManager.cpp

static JNINativeMethod gSystemSensorManagerMethods[] = { 
    {"nativeClassInit",                                //java
            "()V",                                    //argument, return type
            (void*)nativeClassInit },                 //c++

    {"nativeGetNextSensor",
            "(Landroid/hardware/Sensor;I)I",
            (void*)nativeGetNextSensor },
};

static void
nativeClassInit (JNIEnv *_env, jclass _this)
{
    jclass sensorClass = _env->FindClass("android/hardware/Sensor");
    SensorOffsets& sensorOffsets = gSensorOffsets;
    sensorOffsets.name        = _env->GetFieldID(sensorClass, "mName",      "Ljava/lang/String;");
    sensorOffsets.vendor      = _env->GetFieldID(sensorClass, "mVendor",    "Ljava/lang/String;");
    sensorOffsets.version     = _env->GetFieldID(sensorClass, "mVersion",   "I");
    ...
}


JNINativeMethod 형식으로 이렇게 c++의 함수 이름과 java의 framework api 이름을 매핑시켜 놓는다

int register_android_hardware_SensorManager(JNIEnv *env)
{
    jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager",
            gSystemSensorManagerMethods, NELEM(gSystemSensorManagerMethods));        //아까 위에서 매핑했던 이름

    jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager$BaseEventQueue",
            gBaseEventQueueMethods, NELEM(gBaseEventQueueMethods));

    FIND_CLASS(gBaseEventQueueClassInfo.clazz, "android/hardware/SystemSensorManager$BaseEventQueue");
    ...
}

extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);

    ALOGV("Registering %s's %d native methods...", className, numMethods);

    scoped_local_ref<jclass> c(env, findClass(env, className));
    if (c.get() == NULL) {
        char* msg;
        asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className);
        e->FatalError(msg);
    }

    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
        char* msg;
        asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
        e->FatalError(msg);
    }

    return 0;
}

Android RunTime에 서비스 등록

frameworks/base/core/jni/AndroidRuntime.cpp

#define REG_JNI(name)    { name }
struct RegJNIRec {
    int (*mProc)(JNIEnv*);
};

static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_com_android_internal_os_RuntimeInit),
    REG_JNI(register_android_os_SystemClock),
    REG_JNI(register_android_util_EventLog),
    ...
    REG_JNI(register_android_hardware_SensorManager),        //아까 java method와 c++ method를 연결했던 그 함수 포인터
    ...
}

extern "C"
jint Java_LoadClass_registerNatives(JNIEnv* env, jclass clazz) {
    return register_jni_procs(gRegJNI, NELEM(gRegJNI), env);
}

static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
    for (size_t i = 0; i < count; i++) {
        if (array[i].mProc(env) < 0) {
    ...
}    //mProc 즉 RegJNIRec에 등록된 function pointer들이 실행 된다


int AndroidRuntime::startReg(JNIEnv* env)
{
    /*
     * This hook causes all future threads created in this process to be
     * attached to the JavaVM. (This needs to go away in favor of JNI
     * Attach calls.)
     */
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc)
    if(register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
    ...
}

부팅하면 ART에 의해 startReg가 호출되고 struct gREGJNI에 들어간 각종 native funtion들은 register_jni_procs에 의해 모두 등록 된다


Java Application

public class MainActivity extends Activity implements SensorEventListener{
    private SensorManager mSensorManager;
    private Sensor mSensorAccelerometer;
    private float[] tGravity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);

        List<Sensor> lSensor = mSensorManager.getSensorList(Sensor.TYPE_ALL);

        for(int i=0; i<lSensor.size(); i++)
        {
            Log.d("test@debug", String.format("vendor: %s, name: %s, type: %s",
                                lSensor.get(i).getVendor(), lSensor.get(i).getName(), lSensor.get(i).getStringType()));
        }

        mSensorAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mSensorManager.registerListener(this, mSensorAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    protected void onPause() {
        super.onPause();
        mSensorManager.unregisterListener(this);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        switch(event.sensor.getType()) {
            case Sensor.TYPE_ACCELEROMETER:
            default:
                tGravity = event.values.clone();
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
}

Sensor.java - getType, getVendor, getName등의 method들이 정의 되어 있다



실행 - nexus 5x

vendor: Bosch, name: BMI160 accelerometer, type: android.sensor.accelerometer
vendor: Bosch, name: BMI160 gyroscope, type: android.sensor.gyroscope
vendor: Bosch, name: BMM150 magnetometer, type: android.sensor.magnetic_field
...
vendor: Google, name: Double Tap, type: com.google.sensor.double_tap
vendor: Google, name: Device Orientation, type: android.sensor.device_orientation


framework/base/core/java/android/hardware/SensorManager.java

    public boolean registerListener(SensorEventListener listener, Sensor sensor, ...) {
        return registerListenerImpl(listener, sensor, ...);
    }

onResume(), onPause()에서 호출 되는 registerListener method



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

    protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,...) {
        ...
        Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
        queue = new SensorEventQueue(listener, looper, this);
        queue.addSensor(...)
        mSensorListeners.put(listener, queue);
        ...
    }

    private static abstract class BaseEventQueue {
        private static native int nativeEnableSensor(long eventQ, int handle ...)
        private long nSensorEventQueue;

        BaseEventQueue(Looper looper, SystemSensorManager manager) {
            nSensorEventQueue = nativeInitBaseEventQueue(this, looper.getQueue(), mSractch);
            ...
        }
    }

    static final class SensorEventQueue extends BaseEventQueue {
        public SensorEventQueue(SensorEventListener listener, Looper looper,
            SystemSensorManager manager) {
            super(looper, manager);
            mListener = listener;
            }
    }


looper 없으면 생성하고 SensorEvent Queue 만들고 queue에 센서를 추가한다

    public boolean addSensor(
        ...
        addSensorEvent(sensor);
        if(enableSensor(...))
    )
    private int enableSensor(
        Sensor sensor, ...) {
        return nativeEnableSensor(nSensorEventQueue, ...);
    }
static jint nativeEnableSensor(JNIEnv *env, jclass clazz, ...) {
    sp<Receiver> receiver(reinterpret_cast<Receiver *>(eventQ));
    return receiver->getSensorEventQueue()->enableSensor(handle, ...);
}


다시 JNI로 돌아가서 확인해보면
frameworks/base/core/jni/android_hardware_SensorManager.cpp

static JNINativeMethod gBaseEventQueueMethods[] = { 
    {"nativeInitBaseEventQueue",
            "(Landroid/hardware/SystemSensorManager$BaseEventQueue;Landroid/os/MessageQueue;[F)J",
            (void*)nativeInitSensorEventQueue },

    {"nativeEnableSensor",
            "(JIIII)I",
            (void*)nativeEnableSensor },

    {"nativeDisableSensor",
            "(JI)I",
            (void*)nativeDisableSensor },

    {"nativeDestroySensorEventQueue",
            "(J)V",
            (void*)nativeDestroySensorEventQueue },

    {"nativeFlushSensor",
            "(J)I",
            (void*)nativeFlushSensor },
}

java application test code
cts/apps/CtsVerifier/src/com/android/cts/verifier/sensors/HeartRateMonitorTestActivity.java


정리

  • SensorManager = onCreate(), (SensorManager)getSystemService(SENSOR_SERVICE); : java sensor application
  • SensorManager.getSensorList - getType, getVendor, getName등 method 정의 : Sensor.java
  • registerListener - onResume() onPause() : SensorManager.java
  • registerListenerImpl - Looper per SensorEvnentListener : SystemSensorManager.java
  • static final class SensorEventQueue extends BaseEventQueue : SystemSensorManager.java
    addSensor가 호출, 부모의 method가 호출된다 - private static abstract class BaseEventQueue
    -> enableSensor -> nativeEnableSensor
  • static jint nativeEnableSensor - framework/base/core/jni/android_hardware_SensorManager.cpp
  • SensorEventQueue::enableSensor - SensorEventQueue.cpp
  • enableDiasble - frameworks/native/services/sernsorservice/SensorService.cpp
    SensorService::enable
  • SensorDevice& dev(SensorDevice::getInstance());
    singleton 기법으로 instatnce 생성
  • SensorDevice.cpp

기타

input keyevent 62
sendevent
sendevent /dev/input/event
getevent
hex 값으로 데이터 가져옴


Posted by 쵸코케키

2017. 3. 2. 20:51 devel/개념

Linux Kernel DMA

주의

작성자 컴맹임

2017. 03. 02 작성. 지속적으로 커널문서가 업데이트 되니 주의 바람

아 빨리 급해요!!!

  1. dma mask bit 설정 및 확인
  2. 목적에 맞는 타입으로 할당 요청 혹은 매핑 요청
  3. 다 썼으면 해제 혹은 언맵
    drivers/base/dma-mapping.c 에서 EXPORT_SYMBOL 된거 골라서 쓰면 된다.
    include/linux/dma-mapping.h에서 골라써도 되고

참조

kernel DMA 관련 문서들 + 뇌내망상 + 구글링 + kernel code
DMA-API-HOWTO.txt, DMA-API.txt 등등

근데 생각해보니까 이렇게 다 짬뽕으로 섞어서 써놓으면 커널 문서 업데이트 되었을 때 어떻게 하지?망했….

CPU and DMA addresses


kernel에서 kmalloc, vmalloc 들을 사용해서 메모리 할당하면 보통 void* 타입의 virtual address 가 할당 된다.

우리가 흔히 어딘가에서 들어본 적 있을법한 TLB나 page table 등등 이런 애들은 virtual memory system이라고 한다. (TLB에서 스크롤을 내렸습…… 잠깐 기다리라)

이들의 역할은 virtual addressCPU physical address로 변환한다.
이 주소는 phys_addr_t 혹은 resource_size_t 같은 데이터 타입을 사용한다

kernel은 이 physical address를 사용해서 장치를 관리한다.
이 주소들을 보고 싶으면 아래의 커맨드를 사용해보시라

cat /proc/iomem

physical address는 곧바로 사용할 수는 없고 ioremap 함수 같은 것으로 virtual address로 매핑을 해서 사용해야 한다.

그런데 I/O 장치들은 bus address라는걸 쓴다.

대부분 다르지만 몇몇 시스템들은 bus addressCPU physical address와 동일하다. IOMMU 와 host bridge가 위의 두 주소를 서로 매핑해준다.

I/O 장치 관점에서 DMA는 bus address space를 사용하지만 일부의 공간만 DMA가 된다. 예를 들어 메모리와 PCI BAR를 64bit 지원하는 시스템이더라도 IOMMU를 사용할 것이고 얘는 32bit DMA address만 사용할꺼다.

예를 들어 PCI 장치는 BAR라는 영역을 가지고 있다.

만약 kernel이 PCI 장치를 읽고 싶으면 BAR의 주소 bus address를 physical address 로 변환하고 그 다음 ioremap 같은 함수로 physical address를 virtual address로 바꾸어 접근을 하면 된다.
그리고 ioread32 같은 함수를 사용하여 bus address의 PCI device 레지스터에 접근할 수도 있다.

만약 장치가 DMA 지원을 한다면 다음과 같이 사용한다.
kmalloc 같은거로 virtual memory 할당을 받아서 kernel 내부 함수 도움을 받아(virtual memory system) physical address즉 시스템 램 주소로 변환을 한다.

일부는 physical address가 bus address와 동일하여 곧바로 DMA가 가능하지만 대부분의 경우 IOMMU가 DMA 주소로 변환을 해야 한다.
IOMMU 제어를 일반인이 다할 수 없으니 kernel이 제공하는 DMA API를 쓰도록.

Linux 는 dynamic DMA mapping 도 된다. 이게 뭔소린지 모르겠는데 실시간으로 kmalloc 할당해서 DMA하고 free로 제거하고 이런게 된다는 이야기인듯.

요약 #1

  • Kernel - virtual address space
  • Virtual Memory System(TLB, page table, 등) - virtual address space <-> physical address 변환
  • CPU - physical address space
  • IOMMU, host bridge - physical address <-> bus address 변환
  • I/O Device - bus address space
  • DMA - bus address space의 일부 영역만 가능

DMA 자격증

이미 알겠지만 아무거나 DMA가 되는게 아니다.
일단 DMA는 dma_addr_t 타입의 주소로 가능하다고 정했다.

page allocator를 통해 메모리를 할당 받은 경우
예 : __get_free_page, kmalloc, kmem_cache_alloc 된다

vmalloc을 통해 할당 받은 경우는 안된다.
핵심은 물리적으로 선형이 아니라서 그러는데 뭐라고 설명해야 할지 모르겠다 원문 참조 바람

This means specifically that you may _not_ use the memory/addresses
returned from vmalloc() for DMA.  It is possible to DMA to the
_underlying_ memory mapped into a vmalloc() area, but this requires
walking page tables to get the physical addresses, and then
translating each of those pages back to a kernel address using
something like __va().  [ EDIT: Update this when we integrate
Gerd Knorr's generic code which does this. ]

vmalloc 에서 dma가 가능하도록 scatter gathering 처럼 각각 physical address를 획득 한 다음 IOMMU로 선형주소 인 것 처럼 붙여서 bus address에 매핑한다는 건지 잘 모르겠다 작업중이라는거 보니 꽤 이런 유사한 요구가 많았나보다

물론 kernel image address 즉 data, text, bss segment들도 당연히 안된다. 이런걸 하려고 시도 한게 변태 아닌가? stack, module image address 역시 안된다.

DMA addressing limitations

기본으로 kernel은 32bit DMA를 다 다룰 수 있다고 가정한다. 만약 64bit 가능한 장비를 위해서는 증가시켜야 할 수도 있겠다만 장비 자체 스펙 한계로 일부 영역으로 줄여야 할 수도 있다.
말이 헷갈리는데 64bit kernel 시스템이라고 해서 64bit DMA가 되는게 아니다.

PCI-X 는 IO bus가 PCI-X mode로 운영되기 위해서 64bit addressing(DAC) 가 필요하다.
DMA controller에게 설정을 교류하는 방법들이 있다.

int dma_set_mask_and_coherent(struct device *dev, u64 mask);
int dma_set_mask(struct device *dev, u64 mask);
int dma_set_coherent_mask(struct device *dev, u64 mask);
//dev - device, mask - 너의 장비 몇 bit 지원하니?

이런 함수들은 보통 다음과 같이 사용된다
pdev, pci 등등 probe -> &pdev->device를 인자로 사용, mask 를 시스템 환경에 맞게 설정
걍 kernel 에서 샘플 코드 보는게 빠를듯

    if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) {
        dev_warn(dev, "mydev: No suitable DMA available\n");
        goto ignore_this_device;
    }

if success
return 0
else

/* 보통 실패하는 원인들 */
1. DMA mask 설정 잘못함
2. DMA mode 자체 지원 안 함
3. 무시하고 쓰지마! -> 진짜 이렇게 나와있음 -.-;;;

64bit는 다음과 같은 형식으로 접근해보란다.

64bit streaming DMA 예제

    int using_dac;

    if (!dma_set_mask(dev, DMA_BIT_MASK(64))) {
        using_dac = 1;
    } else if (!dma_set_mask(dev, DMA_BIT_MASK(32))) {
        using_dac = 0;
    } else {
        dev_warn(dev, "mydev: No suitable DMA available\n");
        goto ignore_this_device;
    }

64bit consistent allocation DMA 예제

    int using_dac, consistent_using_dac;

    if (!dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) {
        using_dac = 1;
        consistent_using_dac = 1;
    } else if (!dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) {
        using_dac = 0;
        consistent_using_dac = 0;
    } else {
        dev_warn(dev, "mydev: No suitable DMA available\n");
        goto ignore_this_device;
    }

24bit DMA 예제

    if (dma_set_mask(dev, DMA_BIT_MASK(24))) {
        dev_warn(dev, "mydev: 24-bit DMA addressing not available\n");
        goto ignore_this_device;
    }

Types of DMA mappings

A. Consistent DMA mapping

Think of “consistent” as “synchronous” or “coherent”.
일반적으로 드라이버의 초기화에 매핑되고 끝날 때 언맵 된다. 이를 위해 하드웨어는 반드시 다음과 같은 것들을 보장해야 한다. device와 CPU는 data를 병렬로 access할 수 있어야 하고 둘 다 어떠한 명시적 software flushing 없이 데이터 업데이트를 확인할 수 있어야 한다.
(예를 들면 linux의 sync 명령어 같은 처리를 안 해도 되도록)

Consistent DMA mappings which are usually mapped at driver initialization, unmapped at the end and for which the hardware should   guarantee that the device and the CPU can access the data in parallel and will see updates made by each other without any explicit software flushing.

현재 기본은 DMA space의 하위 32bit consistent memory를 return 하는 것이다.
그러나 미래 호환성을 위해서 지금 잘 동작하더라도 consistent mask를 설정 해 두도록.
머라는건지……64bit 체크루틴 넣어두라고?

 The current default is to return consistent memory in the low 32
  bits of the DMA space.  However, for future compatibility you should
  set the consistent mask even if this default is fine for your
  driver.

Good examples of what to use consistent mappings for are:

- Network card DMA ring descriptors.
- SCSI adapter mailbox command data structures.
- Device firmware microcode executed out of main memory.

cpu가 명령어를 reorder 할 수 있으니까 순서가 중요한 코드의 경우 memory barrier를 잘 써라 이런 경고 인거 같음.

IMPORTANT: Consistent DMA memory does not preclude the usage of
proper memory barriers.  The CPU may reorder stores to consistent memory just as it may normal memory.  

Example: if it is important for the device to see the first word
of a descriptor updated before the second, you must do something like:

    desc->word0 = address;
    wmb();
    desc->word1 = DESC_VALID;

in order to get correct behavior on all platforms.

Also, on some platforms your driver may need to flush CPU write
buffers in much the same way as it needs to flush write buffers
found in PCI bridges (such as by reading a register's value
after writing it).

B. Streaming DMA mapping

Think of “streaming” as “asynchronous” or “outside the coherency domain”.
1회의 DMA 전송을 위해 일반적으로 사용된다는데? 사용 끝나면 언맵하고
하드웨어는 sequential access를 위해 최적화 할 수 있다(뭘 최적화???)

Good examples of what to use streaming mappings for are:

- Networking buffers transmitted/received by a device.
- Filesystem buffers written/read by a SCSI device.

이 매핑타입의 인터페이스의 경우 하드웨어에 하고 싶은거 다 해서 성능을 높일 수 있다.
이런 내용인거 같다

  The interfaces for using this type of mapping were designed in
  such a way that an implementation can make whatever performance
  optimizations the hardware allows.  To this end, when using
  such mappings you must be explicit about what you want to happen.

위의 두 DMA 매핑 타입들은 bus로부터 오는 alignment 제한은 없다. 물론 있는 모델도 있지만 말야.
또한 buffer가 cache line을 다른 데이터와 공유하지 않을 때 시스템과 DMA-coherent 하지 않은 cache는 더 일 잘할꺼임.

Also, systems with caches that aren't DMA-coherent will work better
when the underlying buffers don't share cache lines with other data.

뭔 소린지 잘 모르겠는데 예를 들어 네트워크 카드로부터 트래픽 데이터가 시스템 메모리로 들어오는데 cache에 계속 등록된다면 되려 system 전체 퍼포먼스에 악영향을 줄 수도 있다 이런 맥락으로 보면 되려나? 왜냐면 그 데이터는 cache update하는 의미가 없으니까
그래서 저래 써놓은건가 싶다.

이 링크를 참조해본다
http://junbenchmarking.tistory.com/entry/Linux-%EC%BB%A4%EB%84%90%EC%97%90%EC%84%9C-CPU-%EC%99%80-DMA-%EC%82%AC%EC%9D%B4

DMA API

먼저 DMA API를 사용하고 싶다면 이것부터 하시라

#include <linux/dma-mapping.h>

mask 설정 체크 하는 것은 윗쪽의 DMA addressing limitations 항목을 참고

Consistent DMA mapping

할당

void *
dma_alloc_coherent(struct device *dev, size_t size,
                 dma_addr_t *dma_handle, gfp_t flag)
void *
dma_zalloc_coherent(struct device *dev, size_t size,
                 dma_addr_t *dma_handle, gfp_t flag)

사용 예시

dma_addr_t    dma_handle;
cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, gfp);

이 함수는 RAM을 Bytes size만큼 consistent memory를 할당한다. __get_free_pages() 랑 비슷하다.

arguments

  • dev - 생략
  • size - Bytes 단위. 할당 받고 싶은 크기
  • dma_handle - 아래 return value에서 확인 바람.
  • flag - kmalloc 호출할 때 처럼 사용하는 flag. 심지어 동일하다.

return value

  • virtual address - 할당된 메모리 주소, CPU와 dma_handle을 통해 access할 수 있다
  • NULL - 뭐긴 뭐야 할당에 실패한거지**
  • dma_handle - 이 인자에는 device의 bus width와 동일한 unsigned int형이 type casting 된 DMA address가 전달된다.

CPU virtual address와 DMA address는 요구된 메모리 사이즈에 맞는 최소한의 PAGE_SIZE order 에 align되도록 보장한다.

예를 들어 63KB를 요청해도 PAGE_SIZE order에 align맞춰서 64KB가 할당 된다 이 소리인듯

만약에 page_size 보다 작은 크기가 필요하다면 dma_poll 인터페이스를 사용하시라.
일반적으로 할당에 성공하면 32bit virtual address가 return 된다. dma_pool 역시 마찬가지

몇몇의 플랫폼에서 consistent memory는 굉장히 비쌀 수 있다. 그리고 가장 작은 할당 크기도 PAGE_SIZE 만큼 클 수 있다. 그래서 너 반드시 한 번에 퉁쳐서 요청해야 한다(오역?).

/* you should consolidate your requests for consistent memory as much as possible. */

먼소린지 잘 모르겠는데 1KB 요청해도 PAGE_SIZE로 나올 수 있으니 필요한 자료형 합쳐서 한 번에 크게 할당 받으라는 소리 같은데?

그리고 이 함수는 cache effect를 걱정할 필요 없다. 다시말해 dma로 데이터가 들어왔을 때 cpu의 cache에 이전 데이터가 있지 않을까? 하는 걱정을 할 필요 없다.
단 device에 dma로 write할 때 memory와 cpu cache의 동기화를 위해 flush를 해야할 수도 있음에 유의하라.(아까는 걱정하지 말라매!)

해제

void
dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,
               dma_addr_t dma_handle)

별건 없구요 cpu_addr은 할당 받은 virtual address를 넣어야 한다.

Streaming Mapping

할당 및 해제 #1

dma_addr_t
dma_map_single(struct device *dev, void *cpu_addr, size_t size,
              enum dma_data_direction direction)

void
dma_unmap_single(struct device *dev, dma_addr_t dma_addr, 
                 size_t size, enum dma_data_direction direction)

dma_alloc_coherent와 다르게 이 함수는 메모리를 미리 kmalloc 같은거로 알아서 할당을 시킨 상태로 호출해야 한다. 그러면 DMA 주소가 리턴 된다.

arguments

  • dev - 생략
  • cpu_addr - 매핑할 메모리 주소. kmalloc 처럼 physically contiguous 해야 한다. vmalloc이나 scatter/gather 지원 불가.
  • size - 생략

DMA direction

  • DMA_BIDIRECTIONAL - direction isn’t known
  • DMA_TO_DEVICE - from: main memory. to: device
  • DMA_FROM_DEVICE - from: device. to: main memory
  • DMA_NONE - for debugging

streaming mapping만이 direction을 특정화 한다.
consistent mapping은 DMA_BIDIRECTION을 암묵적으로 사용하도록 한다.
direction을 명시화 하여 플랫폼 로우레벨 코드에서 최적화를 할 수 있다는 것 같다.

메모리의 DMA 주소는 반드시 device의 dma_mask이내여야 한다.
무슨 말인고 하니 우리가 cpu_addr로 virtual memory address를 넘겨야 하니까 그 메모리 주소를 미리 잘 device의 특징에 맞게 준비 해놓으라는 말이다.

예를 들면 x86 시스템에서 너가 ISA device 장치를 쓰고 싶어. 그러면 kmalloc으로 메모리 할당할 때 GFP_DMA flag를 써서 처음 16MB 이내 주소의 메모리를 할당받은 다음에 매핑을 요청해! 이런식으로 dma_map_single 호출 하기 전 virtual memory를 할당 받아둘 때 반드시 flag를 잘 설정해서 physical address가 device 스펙에 맞도록 준비를 잘 하자.

또한 위에서 말한 physical contiguity 와 dma_mask 제한은 플랫폼이 IOMMU를 가지고 있다면 적용이 안될 수도 있음

cache cohereny 관련 문제

주의: Memory coherency는 cache line 폭(width) 이라고 불리는 granularity에 의해 동작한다. 이 API에 매핑된 메모리를 올바르게 동작시키기 위해서는 매핑된 영역은 반드시 하나의 cache line boundary에 맞춰 시작해야하고 정확히 끝나야 한다. (하나의 캐시라인에 두개의 독립된 영역이 매핑되는 것을 막기 위해) 근데 문제가 컴파일 타임에는 cache line크기를 알 수 없기 때문에 API는 이 요건 사항을 강제 못한다. 그러므로 cache line 사이즈고뭐고 고려 안 하는 드라이버 개발자는 걍 page boundary에서 시작하고 끝나는 virtual memory 영역만 매핑하는걸 추천한다.

정확하게 이해가 안 가는데 만약에 ioremap등을 통해 물리적으로 선형인 메모리 공간을 얻었는데 이 공간을 2KB로 나누어서 map을 요청하는 변태짓을 했다고 가정하면 cache line width가 4KB라고 가정했을 때 다른 메모리 2개 내용이 한 번에 캐시 되기 때문에 바보된다고 하지 말라는거 같은데??

주의 사항 - direction flag

  • DMA_TO_DEVICE - 메모리에 쓸꺼 다 쓰고 sync 완료 한 뒤 device에 DMA 해야 함. 그 뒤 device에게 메모리는 마치 read-only 인 것 처럼 취급 당한다. 만약에 device가 메모리에 write할 일이 있으면 반드시 DMA_BIDIRECTION으로 설정하도록!
  • DMA_FROM_DEVICE - 반대로 driver가 data에 access해서 읽기 전, 혹은 다 읽어 간 다음 sync 해야 한다. driver입장에서 메모리는 read-only 로 간주된다.
  • DMA_BIDIRECTIONAL - 특별 관리 요망. driver는 device에서 데이터가 발송 된 이후 수정될지 아닐지 쓸지 머할지 확신할 수 없을 때 사용. 그렇기에 항상 device와 메모리 사이에 각각 2회 sync를 해줘야 한다. A. 메모리에서 device로 데이터 발송 되었을 때 sync(확실히 하기 위해 모든 메모리의 변경 사항이 CPU에서도 flush 되야 한다. cache 문제 때문인거 같음.), B. device에서 메모리로 데이터 발송 되었을 때 반드시 sync요망(확실히 하기 위해 device에서 변화가 감지되면 어떤 CPU cache line들이라도 데이터가 업뎃 되야 한다).

걍 원문 보는게 이해가 빠를지도

DMA_BIDIRECTIONAL requires special handling: it means that the driver
isn't sure if the memory was modified before being handed off to the
device and also isn't sure if the device will also modify it.  Thus,
you must always sync bidirectional memory twice: once before the
memory is handed off to the device (to make sure all memory changes
are flushed from the processor) and once before the data may be
accessed after being used by the device (to make sure any processor
cache lines are updated with data that the device may have changed).

streaming mapping 예제

To map a single region, you do:

struct device *dev = &my_dev->dev;
dma_addr_t dma_handle;
void *addr = buffer->ptr;
size_t size = buffer->len;

dma_handle = dma_map_single(dev, addr, size, direction);
if (dma_mapping_error(dev, dma_handle)) {
    /*
     * reduce current DMA mapping usage,
     * delay and try again later or
     * reset driver.
     */
    goto map_error_handling;
}

and to unmap it:

dma_unmap_single(dev, dma_handle, size, direction);

dma_mapping_error 함수 안 쓰고 직접 주소값 비교하거나 errno 비교 같은짓 하지 마라

위 예제 처럼 있는 그대로 잘 쓰도록.

할당 및 해제 #2

dma_addr_t
dma_map_page(struct device *dev, struct page *page,
            unsigned long offset, size_t size,
            enum dma_data_direction direction)

void
dma_unmap_page(struct device *dev, dma_addr_t dma_address, 
               size_t size, enum dma_data_direction direction)

page를 직접 연결 시킬 수도 있다.

그런데 offset이랑 size 는 각 page 매핑에서 사용되는 파라메터인데 cache width를 잘 모르겠다 싶으면 걍 이 함수를 쓰지 마

할당 및 해제 #3

dma_addr_t
dma_map_resource(struct device *dev, phys_addr_t phys_addr, 
                 size_t size, enum dma_data_direction dir, unsigned long attrs)

void
dma_unmap_resource(struct device *dev, dma_addr_t addr, size_t size,
           enum dma_data_direction dir, unsigned long attrs)

MMIO device를 다룰 때 쓰시라. RAM은 매핑하면 안된다. 이 말이 바로 이해가 안간다면 MMIO에 대해서 검색 ㄱㄱ

page를 직접 지정할 수도 있다.

다만 HIGHMEM을 가지고는 할 수 없다는데 어차피 64bit에서 돌릴꺼면 신경쓰지 말자

struct device *dev = &my_dev->dev;
dma_addr_t dma_handle;
struct page *page = buffer->page;
unsigned long offset = buffer->offset;
size_t size = buffer->len;

dma_handle = dma_map_page(dev, page, offset, size, direction);
if (dma_mapping_error(dev, dma_handle)) {
    /*
     * reduce current DMA mapping usage,
     * delay and try again later or
     * reset driver.
     */
    goto map_error_handling;
}

...

dma_unmap_page(dev, dma_handle, size, direction);

예제

my_card_setup_receive_buffer(struct my_card *cp, char *buffer, int len)
{
    dma_addr_t mapping;

    mapping = dma_map_single(cp->dev, buffer, len, DMA_FROM_DEVICE);
    if (dma_mapping_error(cp->dev, mapping)) {
        /*
         * reduce current DMA mapping usage,
         * delay and try again later or
         * reset driver.
         */
        goto map_error_handling;
    }

    cp->rx_buf = buffer;
    cp->rx_len = len;
    cp->rx_dma = mapping;

    give_rx_buf_to_card(cp);
}

...

my_card_interrupt_handler(int irq, void *devid, struct pt_regs *regs)
{
    struct my_card *cp = devid;

    ...
    if (read_card_status(cp) == RX_BUF_TRANSFERRED) {
        struct my_card_header *hp;

        /* Examine the header to see if we wish
         * to accept the data.  But synchronize
         * the DMA transfer with the CPU first
         * so that we see updated contents.
         */
        dma_sync_single_for_cpu(&cp->dev, cp->rx_dma,
                    cp->rx_len,
                    DMA_FROM_DEVICE);

        /* Now it is safe to examine the buffer. */
        hp = (struct my_card_header *) cp->rx_buf;
        if (header_is_ok(hp)) {
            dma_unmap_single(&cp->dev, cp->rx_dma, cp->rx_len,
                     DMA_FROM_DEVICE);
            pass_to_upper_layers(cp->rx_buf);
            make_and_setup_new_rx_buf(cp);
        } else {
            /* CPU should not write to
             * DMA_FROM_DEVICE-mapped area,
             * so dma_sync_single_for_device() is
             * not needed here. It would be required
             * for DMA_BIDIRECTIONAL mapping if
             * the memory was modified.
             */
            give_rx_buf_to_card(cp);
        }
    }
}

별건 아니고 dma buffer를 setup하고 packet이 들어와서 interrupt가 뜨면 먼저

Driver에서 더이상 virt_to_bus()나 bus_to_virt() 같은 api를 쓰면 안된다.
더이상 bus_to_virt() 같은 dynamic DMA mapping scheme은 없단다.
반드시 dma_alloc_coherent(), dma_pool_alloc(), dma_map_single() 같은 함수를 써야 한단다. (dma_map_sg()는 하드웨어로 다이나믹 매핑 해주는거고)

Here, “offset” means byte offset within the given page.

스캇…리스트……..가 아니라 scatterlists 기법도 있다.

할당 및 해제 #4 - scatter/gathering

int
dma_map_sg(struct device *dev, struct scatterlist *sg,
        int nents, enum dma_data_direction direction)

void
dma_unmap_sg(struct device *dev, struct scatterlist *sg,
        int nents, enum dma_data_direction direction)

arguments

  • dev - 생략

  • sg - sg_init_table을 통해 할당하고 set_set_page 혹은 set_set_buf를 통해 설정한 sg list 변수로 추정된다. 이 부분은 별도 항목으로 굉장히 길다.

  • nents - 필요한 sglist 엔트리의 수(the number of entries in the sglist.)

  • direction - 생략

return value

정상 : 매핑된 DMA address segments 수

실패 : 0

주의 : 만약 sg list의 몇몇 entry가 물리적으로 혹은 virtually하게 인접해있고 IOMMU가 그들을 하나의 entry로 매핑한다면 인자로 넣은 nents 값 보다 적은 값이 리턴될 수 있다.

sg를 중복해서 매핑하는 변태짓 하지 마라. 이전 매핑 내용 파괴된다.

예제 - scatter/gathering

With scatterlists, you map a region gathered from several regions by:

int i, count = dma_map_sg(dev, sglist, nents, direction);
struct scatterlist *sg;

for_each_sg(sglist, sg, count, i) {
    hw_address[i] = sg_dma_address(sg);
    hw_len[i] = sg_dma_len(sg);
}

혹은 sg_next 같은 API도 있다

lib/scatterlist.c 참고

매우 복잡 예제 - drivers/spi/spi.c

  1. vmalloc인지 kmap을 사용한 제품인지 메모리 종류를 확인한다.
  2. sg_alloc_table을 호출해서 적당히 sg table을 할당 받는다.
  3. 메모리를 page로 변환(vmalloc_to_page 혹은 kmap_to_page)
  4. sg_set_page, sg_set_buf로 sg table의 sg list 설정을 한다
  5. dma_map_sg
  6. 하 시밤 개 복잡

SYNC

void
dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle, 
                        size_t size, 
                        enum dma_data_direction direction)
void
dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle, 
                           size_t size, 
                           enum dma_data_direction direction)
void
dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, 
                    int nents, enum dma_data_direction direction)
void
dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
                       int nents, enum dma_data_direction direction)

연속적인 싱글 혹은 sg 매핑을 sync 한다. 파라메터는 single 매핑 api 호출했을 때와 동일 해야 한다. 그리고 dma_handle과 size 파라메터를 사용할 수 있는데 부분 sync를 위해 다르게 사용해도 ok

DMA ATTR

gg……

DMA-attributes.txt 를 직접 확인하기 바란다. 특수한 경우에 사용한다.

DMA_ATTR_NO_KERNEL_MAPPING, DMA_ATTR_NON_CONSISTENT 등등 희안한게 많다.

Handling Errors

Example 1:
    dma_addr_t dma_handle1;
    dma_addr_t dma_handle2;

    dma_handle1 = dma_map_single(dev, addr, size, direction);
    if (dma_mapping_error(dev, dma_handle1)) {
        /*
         * reduce current DMA mapping usage,
         * delay and try again later or
         * reset driver.
         */
        goto map_error_handling1;
    }
    dma_handle2 = dma_map_single(dev, addr, size, direction);
    if (dma_mapping_error(dev, dma_handle2)) {
        /*
         * reduce current DMA mapping usage,
         * delay and try again later or
         * reset driver.
         */
        goto map_error_handling2;
    }

    ...

    map_error_handling2:
        dma_unmap_single(dma_handle1);
    map_error_handling1:
Example 2: (if buffers are allocated in a loop, unmap all mapped buffers when mapping error is detected in the middle)
    dma_addr_t dma_addr;
    dma_addr_t array[DMA_BUFFERS];
    int save_index = 0;

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

        ...

        dma_addr = dma_map_single(dev, addr, size, direction);
        if (dma_mapping_error(dev, dma_addr)) {
            /*
             * reduce current DMA mapping usage,
             * delay and try again later or
             * reset driver.
             */
            goto map_error_handling;
        }
        array[i].dma_addr = dma_addr;
        save_index++;
    }

    ...

    map_error_handling:

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

        ...

        dma_unmap_single(array[i].dma_addr);
    }

별건 아니고 이런 형식으로 작성하면 좋다. 에러났을 때 이전까지 매핑했던걸 해제하거라.

Platform Issues

1. Struct scatterlist Requirements

scatterlist 즉 CONFIG_NEED_SG_DMA_LENGTH는 IOMMU가 지원해야 한다.

2. ARCH_DMA_MINALIGN

Architecture는 반드시 kmalloc으로 할당한 buffer는 DMA-safe하도록 확증해야 한다

우리는 할 수 없지만 일단 이렇게 알고 있자. kmalloc 할당된 메모리는 다 DMA 되는구나!

만약 fully DMA-coherent 하지 않는다면. 예를 들어 CPU cache와 main memory의 데이터가 같다고 Architecture가 보증하지 않는 시스템의 경우 ARCH_DMA_MINALIGN 가 반드시 설정 되어야 한다

이 설정을 하면 kmalloc으로 할당한 메모리는 cache line을 다른 것들과 공유하지 않는다.

예제는 arch/arm/include/asm/cache.h 에서 확인 바란다

ARCH_DMA_MINALIGN은 DMA 메모리 alignment constraints에 관한 것이다

일반적인 data alignment constraints에 대해 걱정 ㄴㄴ해

뭐라는건지 모르겠다

cache.h 주석 보면 kmalloc에 의해 할당된 메모리가 DMA에 사용될 수 있으니 우리는 cache aligned 되도록 모든 할당을 맞춰주겠노라. 반면에 관련 되어있지 않은 코드는 DMA 전송이 끝나기 전 cache에 있는 오래된 데이터를 읽어가는 문제를 일으킬 수 있다.

할당하는 메모리 크기를 cache align 맞추는거랑 cache update랑 먼 상관일까?


Posted by 쵸코케키



Linux Input Event Driver

주의

내공 부족으로 정확하지 않는 내용이 다수 있을 수 있음.
Subsystem이라고 부를 정도로 정말 거대하다.

0. The Input Subsystem

출처 : Essential Linux Device Drivers

hw -> Transfer Layer{spi, i2c, ….} -> input driver -> i2c core api -> input event driver -> Evdev Interface -> application

내가 코드 보며 이해한 바로는 이런데 실제로는 어떨련지 모르겠다.

굳이 input driver와 input event driver를 나누지 않고 하나로 합쳐서 작성된 것도 있다.


1. The Evdev Interface

include/linux/input.h
input subsystem의 자료형 및 정의가 있다

struct input_event {
  struct timeval    time;    //timestamp
  __u16                type;
  __u16                code;
  __s32                value;    //event value
}

이 구조체 형태로 /dev/input/ 디렉토리에 이벤트 값이 전달 된다.
주의 : 한 회에 한 종류 값만 전달 된다.

무슨 의미인고 하니 read로 읽을 때 이벤트 당 1개씩 값이 전달 된다는 이야기

예를 들어 (x, y) 좌표에 (12, 34) 클릭이 발생한 경우
data#1 - 클릭 발생
type = EV_KEY,
code = BTN_TOUCH,
value = 1

data#2 - x 좌표
type = EV_ABS,
code = ABS_X,
value = 12

data#3 - y 좌표
type = EV_ABS,
code = ABS_Y,
value = 34

data#4 - 이벤트 끝
type = EV_SYN, //event1 finished
code = SYN_REPORT,
value = 0

EV_SYN으로 하나의 이벤트 전송 종료를 알린다.

이벤트 리포팅을 위해

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);
...
static inline void input_sync(struct input_dev *dev);

여러 이벤트 종류에 따라 각기 다양하게 함수들이 있다.


사용 예시
drivers/input/joystick/a3d.c

static void a3d_read(struct a3d *a3d, unsigned char *data)
{
  ...
  input_report_key(dev, BTN_RIGHT, data[2] & 1);
  input_report_key(dev, BTN_LEFT, data[3] & 2);
  ...
  input_report_abs(dev, ABS_X, ....);
  ...
  input_sync(dev);
}

data를 얻어와서 event 를 evdev로 보내는 모습

이 드라이버의 경우 data는 별도의 input driver에게 얻는다.

struct input_dev {
  ...
  struct input_id    id;
  unsigned long evbit[...];
  unsigned long keybit[...];
  ...
  int (*open)(struct input_dev *dev);
  void (*close)(struct input_dev *dev);
  ...
}

엄청나게 거대하므로 각각 역할은 커널소스의 주석을 직접 확인하기 바란다.


2. Example Source

drivers/input/keyboard/pxa27x_keypad.c

이 코드가 가장 보기 쉬웠어요

module_platform_driver
-> probe
-> request_mem_region, ioremap
-> input_allocate_device, input_dev 설정, request_irq
-> input_register_device

irq_handler - mmio read, input_event, input_report_key, input_sync
대략 이런 흐름인 것 같다.


3. Linux input event read Application code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#include <sys/time.h>
#include <string.h>

int main()
{
    int fd, ret;
    int x, y;
    const char* evdPath = "/dev/input/event3";
    struct input_event iev[3];

    fd = open(evdPath, O_RDONLY);

    if(fd < 0) {
        perror("error");
        return -1;
    }

    while(1) {
        ret = read(fd, iev, sizeof(struct input_event)*3);
        if(ret < 0) {
            perror("error");
            break;
        }

        if(iev[0].type == EV_REL && iev[0].code == REL_X)
            x = iev[0].value;
        if(idev[1].type == EV_REL && iev[1].code == REL_Y)
            y = iev[1].value;

        printf("x:%d, y:%d\n", x, y);
        printf("%hu, %hu, %d\n", iev[0].type, iev[0].code, iev[0].value);
        printf("%hu, %hu, %d\n", iev[1].type, iev[1].code, iev[1].value);
        printf("%hu, %hu, %d\n", iev[2].type, iev[2].code, iev[2].value);
    }

    close(fd);
    return 0;
}

RUN

4, 3
2, 0, 4
2, 1, 3
0, 0, 0
...

별 설명할 꺼리가 없다……
다만 왜 event3로 생성이 되는지 그건 잘 모르겠다.


4. virtual input event driver code

이름은 거창한데 사실 별거 없다
hw 에서 인터럽트 혹은 데이터를 안 받았는데 그냥 데이터 들어왔다고 evdev에게 이벤트를 알리는 것이다.

const char                 *pdev_name = "ietest";
struct platform_device    *pdev;
struct ev_test {
      struct input_dev    *input_dev;
      struct task_struct    *task;
};

int read_data_from_sensor(void)
{
  static int roll = 0;
  return roll++%2 ? 3 : 4;
}

void set_input_device_property(struct input_dev *dev)
{
  set_bit(EV_REL, dev->evbit);
  set_bit(REL_X, dev->relbit);
  set_bit(REL_Y, dev->relbit);
}

void send_event_msg_to_evdev(struct input_dev *dev)
{
  input_report_rel(dev, REL_X, read_data_from_sensor());
  input_report_rel(dev, REL_Y, read_data_from_sensor());
  input_sync(dev);
}

static int hw_fake_int_handler(void *arg)
{
  struct ev_test *event_ctrl = (struct ev_test*) arg;
  struct input_dev *idev = event_ctrl->input_dev;

  while(!kthread_sould_stop())        //check stop signal
  {
    send_event_msg_to_evdev(idev);
    msleep_interruptible(300);
  }

  return 0;
}

/* REMOVED ERROR CHECKING ROUTINE */
static int inputevent_test_init(void)
{
  int ret;
  struct ev_test        *event_ctrl;
  struct task_struct    *task;
  struct input_dev        *idev;

  /* platform or i2c or spi or serio or etc ... */
  pdev = platform_device_register_simple(pdev_name, -1, NULL, 0);

  event_ctrl = kzalloc((sizeof(struct ev_test)), GFP_KERNEL);

  idev = input_allocate_device();
  set_input_device_property(idev);
  input_register_device(idev);

  task = kthread_run(hw_fake_int_handler, event_ctrl, 
                          "ieint_%s_#%d", pdev_name, 1);

  event_ctrl->input_dev = idev;
  event_ctrl->task = task;
  dev_set_drvdata(&pdev->dev, event_ctrl);

  return 0;
}

static void inputevent_test_exit(void)
{
  struct ev_test    *event_ctrl = (struct ev_test*)dev_get_drvdata(&pdev->dev);
  kthread_stop(event_ctrl->task);

  input_unregister_device(pdev);  
}

module_init(inputevent_test_init);
module_exit(inputevent_test_exit);

이 코드에서는 pdev를 가상으로 만들었지만 실제로는 여러 통신 방식을 통해 probe 되고 거기에 interrupt handler를 등록해서 input event 를 보내는 식으로 처리하는 것 같다
인터럽트 핸들러 대신 가상으로 커널 쓰레드를 만들어서 계속 좌표를 보내도록 개발했다


기타 github에 올려본 예제

https://github.com/chocokeki/input_event_driver

Posted by 쵸코케키

2017. 2. 17. 10:41 devel/code

argument parsing

http://linoxide.com/linux-command/use-ip-command-linux/



ip code 참조

Posted by 쵸코케키

황당한 경험인데

대충 makefile은 다음과 같았다



DRVNAME = ietest

obj-m += $(DRVNAME).o

$(DRVNAME)-objs += test_code.o  

...



딱히 오류가 날 껀덕지가 없는데 오류가 나더라

나중에 해결했는데 원인은 바로

DRVNAME = ietest 뒤에 공백이 한 칸 있더라 ㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎ

'devel > etc' 카테고리의 다른 글

4차 산업 우리나라가 준비해야 할 많은 것들  (0) 2017.04.03
perror - Bad file descriptor  (0) 2017.03.30
odroid c2 gpio ioctl controll  (0) 2016.11.22
git 예시  (0) 2016.11.16
Ubuntu 14.04.3 LTS server 에 gui 최소 설치하기  (0) 2016.11.07
Posted by 쵸코케키

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 쵸코케키

Unnamed Structure and Union Fields - Anonymous Struct

검색해보니 대략 구조가 확정적이지 않을 때 사용할 수 있으나 좋은 방법은 아니라는 것 같다
union의 field 중 비구조체와 구조체를 같이 깔끔하게 넣을 수 있다
혹은 자료형을 확정적이지 않게 사용할 수 있다

c로 function overloading 도 가능하다 ㅎㅎ

struct sensors_poll_device_t {
    struct hw_device_t common;
    int (*activate)(struct sensors_poll_device_t *dev,
            int sensor_handle, int enabled);
    int (*setDelay)(struct sensors_poll_device_t *dev,
            int sensor_handle, int64_t sampling_period_ns);
    int (*poll)(struct sensors_poll_device_t *dev,
            sensors_event_t* data, int count);
};

typedef struct sensors_poll_device_1 {
    union {
        /* sensors_poll_device_1 is compatible with sensors_poll_device_t,
         * and can be down-cast to it
         */
        struct sensors_poll_device_t v0;

        struct {
            struct hw_device_t common;

            int (*activate)(struct sensors_poll_device_t *dev,
                    int sensor_handle, int enabled);
            int (*setDelay)(struct sensors_poll_device_t *dev,
                    int sensor_handle, int64_t sampling_period_ns);
            int (*poll)(struct sensors_poll_device_t *dev,
                    sensors_event_t* data, int count);
        };
    };

    int (*batch)(struct sensors_poll_device_1* dev,
            int sensor_handle, int flags, int64_t sampling_period_ns,
            int64_t max_report_latency_ns);
    int (*flush)(struct sensors_poll_device_1* dev, int sensor_handle);
    int (*inject_sensor_data)(struct sensors_poll_device_1 *dev, const sensors_event_t *data);
    void (*reserved_procs[7])(void);
} sensors_poll_device_1_t;

struct sensors_poll_context_t {
    struct sensors_poll_device_t device; // must be first

        sensors_poll_context_t();
        ~sensors_poll_context_t();
    int activate(int handle, int enabled);
    ...

private:
    ...
};

정의는 이렇게 되어있고 실제 사용은 아래와 같다

static inline int sensors_open_1(const struct hw_module_t* module,
        sensors_poll_device_1_t** device) {
    return module->methods->open(module,
            SENSORS_HARDWARE_POLL, (struct hw_device_t**)device);
}

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();
        memset(&dev->device, 0, sizeof(sensors_poll_device_t));


        *device = &dev->device.common;    //이 부분
        ...
}

sensors_poll_device_1_t 에서 struct hw_device_t 로 자유로이 형변환 하여 호출한다
overloading 하는 것 같은 느낌이 난다
왜냐하면 sensors_open_1 입장에서는 device 변수는 hw_device_t 로 활용하여 멤버 변수에 접근할 수 도 있고
아니면 그냥 sensors_poll_device_1 로 그대로 사용하여 batch나 flush 같은 애들을 호출할 수도 있다

https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html

'devel > code' 카테고리의 다른 글

arm hardfault handler  (0) 2017.10.26
argument parsing  (0) 2017.02.17
디버그 on/off에 따라 자료형을 다르게 하는 방법  (0) 2016.12.02
bus_for_each_dev - while 조건문  (0) 2016.10.10
구조체 선언 없이 값 리턴  (0) 2016.04.29
Posted by 쵸코케키

https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#type_signatures


Type Signatures

The JNI uses the Java VM’s representation of type signatures. The following table shows these type signatures.

Java VM Type Signatures
Type SignatureJava Type
Zboolean
Bbyte
Cchar
Sshort
Iint
Jlong
Ffloat
Ddouble
L fully-qualified-class ;fully-qualified-class
[ typetype[]
( arg-types ) ret-typemethod type




frameworks/base/core/jni/* 참조

Posted by 쵸코케키

디버그 on/off에 따라 자료형을 다르게 하는 방법

원래 이렇게 해야하는걸

static const RegJNIRec gRegJNI[] = {
    { 엄청나게 긴 함수명, "엄청나게 긴 함수명" },
    ...
}

요렇게 하면!

#ifdef NDEBUG
    #define REG_JNI(name)      { name }
    struct RegJNIRec {
        int (*mProc)(JNIEnv*);
    };
#else
    #define REG_JNI(name)      { name, #name }
    struct RegJNIRec {
        int (*mProc)(JNIEnv*);
        const char* mName;
    };
#endif

static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_com_android_internal_os_RuntimeInit),
    ...
};

static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
    for (size_t i = 0; i < count; i++) {
        if (array[i].mProc(env) < 0) {
#ifndef NDEBUG
            ALOGD("----------!!! %s failed to load\n", array[i].mName);
#endif
            return -1;
        }
    }
    return 0;
}

깔끔하다 perfect


출처

android/frameworks/base/core/jni/AndroidRuntime.cpp


'devel > code' 카테고리의 다른 글

argument parsing  (0) 2017.02.17
Unnamed Structure and Union Fields - 이름없는 구조체  (0) 2016.12.20
bus_for_each_dev - while 조건문  (0) 2016.10.10
구조체 선언 없이 값 리턴  (0) 2016.04.29
script ㅎㄸㄸ  (0) 2015.08.27
Posted by 쵸코케키

병렬처리 할 때 편하단다

그래서 써보려고 ㅎㅎ


https://golang.org/


https://go-tour-kr.appspot.com


http://knight76.tistory.com/entry/Go-lang-workspace-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0

'devel > 개념' 카테고리의 다른 글

Linux Kernel DMA  (0) 2017.03.02
Linux Input Event Driver  (0) 2017.02.23
gpio pull down switch 연결  (0) 2016.11.16
도요타 캠리 급발진 버그 분석 보고서  (0) 2016.11.10
mmap 구현 - 작성중  (0) 2016.08.11
Posted by 쵸코케키

블로그 이미지
chocokeki
쵸코케키

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.5
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 31

최근에 올라온 글

최근에 달린 댓글

글 보관함