본문 바로가기

Programming/Linux_Kernel

uevent 사용하기

1. netlink 사용

- raw socket : IP 스택 바로위에서 L4 (TCP/UDP) 헤더 지원 없이 프로토콜을 보내거나 받는것으로 거의 network layer 에서 상위 레이어의 패킷을 컨트롤 하기 위함

- netlink socket : kernel 과 kernel 혹은 user space 통신에 사용됨.  데이터를 보내는데는 사용되지 않음

- uevent 를 listen 하기 위해서는 protocol 인자에 NETLINK_KOBJECT_UEVENT 사용


       #include <sys/socket.h>
 

sockfd = socket(int socket_family, int socket_type, int protocol);


< android 의 NetlinkManager.cpp 에서 uevent listen 하는 예제>


int NetlinkManager::start() {

    struct sockaddr_nl nladdr;

    int sz = 2 * 64 * 1024;

    int on = 1;


    memset(&nladdr, 0, sizeof(nladdr));

    nladdr.nl_family = AF_NETLINK;

    nladdr.nl_pid = getpid();

    nladdr.nl_groups = 0xffffffff;


    if ((mSock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC,

            NETLINK_KOBJECT_UEVENT)) < 0) {

        SLOGE("Unable to create uevent socket: %s", strerror(errno));

        return -1;

    }


    if (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) {

        SLOGE("Unable to set uevent socket SO_RCVBUFFORCE option: %s", strerror(errno));

        goto out;

    }


    if (setsockopt(mSock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {

        SLOGE("Unable to set uevent socket SO_PASSCRED option: %s", strerror(errno));

        goto out;

    }


    if (bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {

        SLOGE("Unable to bind uevent socket: %s", strerror(errno));

        goto out;

    }


    mHandler = new NetlinkHandler(mSock);

    if (mHandler->start()) {

        SLOGE("Unable to start NetlinkHandler: %s", strerror(errno));

        goto out;

    }


    return 0;


out:

    close(mSock);

    return -1;

}


2. 일반적으로 udev 가 event 를 처리하나, Android 의 경우 특이하게 init process 가 처리함.

uevent 를 받은 process 는 device specific file 을 만듬.


3. booting 시 user process 가 생성되어 있지 않은 상태에서 uevent 를 보내면, 매세지를 놓치게 된다.

-> /sys/device/[device name]/uevent 에 "add" 를 write 하면, device driver 가 자동으로 uevent 를 다시 보내줌.


4. switch device

-> 내부적으로 uevent 를 사용



이렇게 등록하면 /sys/ 에 uevent 관련 노드가 자동으로 생성됨

root@hammerhead:/sys/devices/virtual/switch/hdmi_audio # ls

name

power

state

subsystem

uevent

root@hammerhead:/sys/devices/virtual/switch/hdmi_audio # cat name              

hdmi_audio

root@hammerhead:/sys/class/switch # ls

h2w

hdmi

hdmi_audio

usb_audio

wfd


아래와 같이 호출해서 상태 변경함.

switch_set_state(&external_common_state->sdev, 0);


실제로는 내부적으로 kobject_uevent_env 함수가 호출됨

void switch_set_state(struct switch_dev *sdev, int state)

{

...

kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp);

...

}


5. JAVA 단에서 uevent listen 하기


- smartdock 의 경우

full source code : https://searchcode.com/codesearch/view/41538946/

@Override public void onCreate() { mUEventObserver.startObserving("DEVPATH=/devices/virtual/switch/smartdock"); mUEventObserver.startObserving("DEVPATH=/devices/virtual/switch/emuconn"); Log.i(LOG_TAG, "Dock Audio service started"); }


...

private final UEventObserver mUEventObserver = new UEventObserver() {
        @Override
        public void onUEvent(UEventObserver.UEvent event) {
            Log.i(LOG_TAG, "DockAudio UEVENT: " + event.toString());

            String name = "";
            int state = 0;
            try {
                name = event.get("SWITCH_NAME");
                state = Integer.parseInt(event.get("SWITCH_STATE"));
            } catch (NumberFormatException e) {
                Log.e(LOG_TAG, "Error parsing switch state!");
            }

            Intent intent = new Intent();
            if (0 == state) { //No Device
                intent.setAction("com.cyanogenmod.dockaudio.ENABLE_SPEAKER_AUDIO");
                Log.i(LOG_TAG, "ENABLE_SPEAKER_AUDIO");
            } else if (!"smartdock".equals(name) && (1 == state || 2 == state)) { // Mono out or Stereo out
                intent.setAction("com.cyanogenmod.dockaudio.ENABLE_ANALOG_AUDIO");
                Log.i(LOG_TAG, "ENABLE_ANALOG_AUDIO");
            } else { //SPDIF audio out, smartdock ("lapdock")
                intent.setAction("com.cyanogenmod.dockaudio.ENABLE_DIGITAL_AUDIO");
                Log.i(LOG_TAG, "ENABLE_DIGITAL_AUDIO");
            }
            sendBroadcast(intent);
            Log.i(LOG_TAG, "Broadcasted intent for state " + state);
        }
    };
}


uevent 가 등록되어 있는 경로를 mUEventObserver.startObserving 함수로 listen 해 놓고, uevent 가 발생하면, onUEvent 함수가 호출됨


- hdmi_audio 의 경우 (Nexus5)

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

WiredAccessoryManager.java


 private static final String NAME_HDMI_AUDIO = "hdmi_audio";


        private List<UEventInfo> makeObservedUEventList() {

...

            // Monitor HDMI

            //

            // If the kernel has support for the "hdmi_audio" switch, use that.  It will be

            // signalled only when the HDMI driver has a video mode configured, and the downstream

            // sink indicates support for audio in its EDID.

            //

            // If the kernel does not have an "hdmi_audio" switch, just fall back on the older

            // "hdmi" switch instead.

            uei = new UEventInfo(NAME_HDMI_AUDIO, BIT_HDMI_AUDIO, 0);

            if (uei.checkSwitchExists()) {

                retVal.add(uei);

            } else {

                uei = new UEventInfo(NAME_HDMI, BIT_HDMI_AUDIO, 0);

                if (uei.checkSwitchExists()) {

                    retVal.add(uei);

                } else {

                    Slog.w(TAG, "This kernel does not have HDMI audio support");

                }

            }


            return retVal;

        }


        @Override

        public void onUEvent(UEventObserver.UEvent event) {

            if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());


            try {

                String devPath = event.get("DEVPATH");

                String name = event.get("SWITCH_NAME");

                int state = Integer.parseInt(event.get("SWITCH_STATE"));

                synchronized (mLock) {

                    updateStateLocked(devPath, name, state);

                }

            } catch (NumberFormatException e) {

                Slog.e(TAG, "Could not parse switch state from event " + event);

            }

        }








uevent 를 이용해 hot plug 가 되었을때 device 등록하기


원문 : https://kshokd.wordpress.com/2012/08/29/init-%EA%B3%BC%EC%A0%95%EC%97%90%EC%84%9C-uevent%EC%99%80-ueventd%EC%9D%98-%ED%99%9C%EC%9A%A9/



uevent와 ueventd는 부팅중 init 과정에서 디바이스 노드를 만드는데 사용된다.

리눅스에서는 다비아스 노드 파일을 생성할수 있게 mknod유틸리티를 제공하지만 안드로이드에서는 보안문제로 인해 제공하지 않는다.

init 프로세스는 다음 두가지 방식으로 디바이스 노드 파일을 생성한다.

1. hot plug : 시스템 동작중 디바이스 장치가 삽입될때 이에대한 이벤트 처리로 ueventd를 거쳐 해당장치의 디비이스 노드 파일을 동적으로 생성

2. cold plug : 미리 정의된 디바이스 정보를 바탕으로 init 프로세스가 실행될때 일괄적으로 디바이스 노드 파일을 생성

cold plug 방식이 있는이유는 hot plug 방식을 사용하려면 이미 ueventd가 띄워져 있어야 하는데 init과정에서 ueventd가 뜨기전에 생성된 디바이스 드라이버 대한 디바이스 노드를 생성하기 위함이다.

cold plug 방식을 자세히 보면

ueventd가 뜨기전의 디바이스 드라이버는 우선 /sys 디렉토리 밑에 디바이스 노드 파일을 생성하는데 필요한 정보를 저장한다.

그후에 ueventd가 뜨면서 디바이스 노드 파일을 생성하지 못한 드라이버에 대하 cold flug처리를 한다.

              


위그림을 참고하면

우선 init과정에서 ueventd를 부른다.

그럼 호출되는 것이 ueventd_main 함수이다.

int ueventd_main(int argc, char **argv)
{
    struct pollfd ufd;
    int nr;
    char tmp[32];

        /* Prevent fire-and-forget children from becoming zombies.
         * If we should need to wait() for some children in the future
         * (as opposed to none right now), double-forking here instead
         * of ignoring SIGCHLD may be the better solution.
         */
    signal(SIGCHLD, SIG_IGN);

    open_devnull_stdio();
    klog_init();

    INFO(“starting ueventd\n”);

    /* Respect hardware passed in through the kernel cmd line. Here we will look
     * for androidboot.hardware param in kernel cmdline, and save its value in
     * hardware[]. */
    import_kernel_cmdline(0, import_kernel_nv);

    get_hardware_name(hardware, &revision);

    ueventd_parse_config_file(“/ueventd.rc”);

    snprintf(tmp, sizeof(tmp), “/ueventd.%s.rc”, hardware);
    ueventd_parse_config_file(tmp);

    device_init();

    ufd.events = POLLIN;
    ufd.fd = get_device_fd();

    while(1) {
        ufd.revents = 0;
        nr = poll(&ufd, 1, -1);
        if (nr <= 0)
            continue;
        if (ufd.revents == POLLIN)
               handle_device_fd();
    }
}

여기서 중요한 함수는 ueventd_parse_config_file와  device_init함수이다.

ueventd_parse_config_file함수는 ueventd.rc파일과 ueventd.%hardware%.rc 파일을 읽어 디바이스 노드 파일을 만드는 정보를 얻는다

여기에 저장되어 있는 정보는 device 이름, permission, gid, uid 이다.

저장되어 있지 않는 device는 디폴트로 600, 0, 0이 세팅된다.

 

device_init함수는 uevent_socket을 열고 coldboot 함수를 실행한다.

여기서 연 소켓은 uevent를 보낼때 쓰이는 것이 아니라 나중에 발생한 uevent를 받을때 쓰인다.

void device_init(void)
{
    suseconds_t t0, t1;
    struct stat info;
    int fd;

    /* is 64K enough? udev uses 16MB! */
    device_fd = uevent_open_socket(64*1024, true);
    if(device_fd < 0)
        return;

    fcntl(device_fd, F_SETFD, FD_CLOEXEC);
    fcntl(device_fd, F_SETFL, O_NONBLOCK);

    if (stat(coldboot_done, &info) < 0) {
        t0 = get_usecs();
        coldboot(“/sys/class”);
        coldboot(“/sys/block”);
        coldboot(“/sys/devices”);
        t1 = get_usecs();
        fd = open(coldboot_done, O_WRONLY|O_CREAT, 0000);
        close(fd);
        log_event_print(“coldboot %ld uS\n”, ((long) (t1 – t0)));
    } else {
        log_event_print(“skipping coldboot, already done\n”);
    }
}

 

호출 되는 coldboot는 내부적으로 do_coldboot를 호출한다.

static void do_coldboot(DIR *d)
{
    struct dirent *de;
    int dfd, fd;

    dfd = dirfd(d);

    fd = openat(dfd, “uevent”, O_WRONLY);
    if(fd >= 0) {
        write(fd, “add\n”, 4);
        close(fd);
        handle_device_fd();
    }

    while((de = readdir(d))) {
        DIR *d2;

        if(de->d_type != DT_DIR || de->d_name[0] == ‘.’)
            continue;

        fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY);
        if(fd < 0)
            continue;

        d2 = fdopendir(fd);
        if(d2 == 0)
            close(fd);
        else {
            do_coldboot(d2);
            closedir(d2);
        }
    }
}

do_coldboot 함수는 재귀호출을 하면서, 하위 마지막 폴더들 밑에 uevent 라는 file 이 있으면, 그것을 open 하고 "add" 를 write 하는 동작을 한다.

openat 에 대한 설명 : http://pinocc.tistory.com/140


디바이스 노드를 생성하지 못한 디바이스가 저장한 /sys 밑의 각각의 해당 폴더를 들어가 uevent 파일에 “add” 메시지를 써넣어 강제로 uevent를 발생시킨다

그후  handle_device_fd 함수를 통해 uevent 를 파싱해 디바이스 노드를 만든다. 이 과정에서 ueventd_parse_config_file에서 얻어온 정보를 사용한다.