| /* alarm management servicest |
| * (C) Copyright Marvell 2015 |
| * Licensed under the GPLv2 |
| * |
| * This service makes sure the alarmtimer & RTC wakeup code is |
| * functioning. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <time.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <pthread.h> |
| #include <assert.h> |
| #include <sys/time.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <linux/input.h> |
| #include <errno.h> |
| #include <asm/ioctl.h> |
| |
| #include <libubox/blob.h> |
| #include <libubox/blobmsg.h> |
| #include <libubox/ustream.h> |
| #include <libubus.h> |
| #include <ubusmsg.h> |
| #include "alarmmanager.h" |
| #include "aoc.h" |
| |
| #ifdef CONFIG_ALARM_SERVICE |
| |
| struct event { |
| struct timeval it_interval; |
| unsigned next_value; |
| int arg; |
| unsigned short flags; |
| char alarm_name[32]; |
| struct event *next; |
| }; |
| |
| static volatile int alarm_update = 0; |
| static pthread_t alarm_tid; |
| /* protect access to event list */ |
| static pthread_mutex_t event_mutex = PTHREAD_MUTEX_INITIALIZER; |
| static struct event *event_queue; |
| static struct event *event_freelist; |
| static int g_maxevents; |
| static int g_events; |
| static int afd; |
| static int dev_opened; |
| |
| static struct blob_buf alarm_buf; |
| static struct ubus_context *alarm_ctx; |
| |
| static void check_event_queue(); |
| static void print_event_queue(); |
| static void thread_alarm(void); |
| size_t strlcpy(char *dst, const char *src, size_t size); |
| |
| size_t strlcpy(char *dst, const char *src, size_t size) |
| { |
| size_t srclen; /* Length of source string */ |
| |
| size --; |
| |
| srclen = strlen(src); |
| |
| if (srclen > size) |
| srclen = size; |
| |
| memcpy(dst, src, srclen); |
| dst[srclen] = '\0'; |
| |
| return (srclen); |
| } |
| |
| static void alarm_notify_complete_cb(struct ubus_request *req, int ret) |
| { |
| (void)(req); |
| (void)(ret); |
| return; |
| } |
| |
| static int alarm_triggered_notify(struct ubus_context *ctx, struct event *event) |
| { |
| struct ubus_notify_request nreq; |
| |
| if (!event->alarm_name[0]) |
| return 0; |
| |
| pthread_mutex_lock(&aoc_ubus_mutex); |
| blobmsg_buf_init(&alarm_buf); |
| blobmsg_add_string(&alarm_buf, "alarm_name", event->alarm_name); |
| if (ubus_notify_async(ctx, &aoc_object, "alarm_triggered", |
| alarm_buf.head, &nreq)) { |
| pthread_mutex_unlock(&aoc_ubus_mutex); |
| return 0; |
| } |
| blob_buf_free(&alarm_buf); |
| nreq.complete_cb = (void *)&alarm_notify_complete_cb; |
| ubus_complete_request(ctx, &nreq.req, 2000); |
| pthread_mutex_unlock(&aoc_ubus_mutex); |
| return 0; |
| } |
| |
| static struct event *get_event_from_freelist(char *name, struct timeval *tv) |
| { |
| struct event *event; |
| if (!event_freelist) |
| return NULL; |
| |
| event = event_freelist; |
| event_freelist = event_freelist->next; |
| event->it_interval = *tv; |
| if (name) |
| strlcpy(event->alarm_name, name, sizeof(event->alarm_name)); |
| event->next = NULL; |
| g_events++; |
| return event; |
| } |
| |
| static void put_event_to_freelist(struct event *event) |
| { |
| memset(event->alarm_name, 0, sizeof(event->alarm_name)); |
| event->next = event_freelist; |
| event_freelist = event; |
| g_events--; |
| return; |
| } |
| |
| static int find_event_and_update(char *name, struct timeval *tv) |
| { |
| struct event *event; |
| |
| pthread_mutex_lock(&event_mutex); |
| if (event_queue) { |
| event = event_queue; |
| while (event) { |
| if (event->alarm_name[0] && |
| !strcmp(event->alarm_name, name)) { |
| break; |
| } |
| event = event->next; |
| } |
| if (event) { /* timer already exist */ |
| event->it_interval = *tv; |
| if (event == event_queue) { |
| alarm_update = 1; |
| if (pthread_kill(alarm_tid, SIGUSR1)) |
| ERROR("%s: pthread_kill failed\n", __func__); |
| } |
| pthread_mutex_unlock(&event_mutex); |
| return 1; |
| } |
| } |
| pthread_mutex_unlock(&event_mutex); |
| return 0; |
| } |
| |
| static int queue_event_to_waitlist(char *name, struct timeval *tv) |
| { |
| struct event *event = NULL; |
| struct event *prev_ev, *cur_ev; |
| int ret; |
| |
| pthread_mutex_lock(&event_mutex); |
| event = get_event_from_freelist(name, tv); |
| if (!event) { |
| pthread_mutex_unlock(&event_mutex); |
| return 1; |
| } |
| |
| if (event_queue) { |
| prev_ev = cur_ev = event_queue; |
| while (cur_ev) { |
| if (timercmp(tv, &(cur_ev->it_interval), <)) { |
| break; |
| } |
| prev_ev = cur_ev; |
| cur_ev = cur_ev->next; |
| } |
| if (cur_ev != event_queue) { |
| DEBUG(2, "New alarm will be inserted into event_queue.\n"); |
| event->next = prev_ev->next; |
| prev_ev->next = event; |
| } else { |
| DEBUG(2, "Set new alarm into top of event_queue,\ |
| and set new alarm.\n"); |
| event->next = event_queue; |
| event_queue = event; |
| alarm_update = 1; |
| if (pthread_kill(alarm_tid, SIGUSR1)) |
| ERROR("%s: pthread_kill failed\n", __func__); |
| } |
| print_event_queue(); |
| check_event_queue(); |
| } else { /* event_queue empty */ |
| event_queue = event; |
| event_queue->flags = TFLAG_NONE; |
| event_queue->next = NULL; |
| |
| /* if more than 1 event, monitor thread */ |
| ret = pthread_create(&alarm_tid, NULL, (void *)thread_alarm, NULL); |
| if (ret) { |
| ERROR("[%s] pthread_create error\n", __FUNCTION__); |
| pthread_mutex_unlock(&event_mutex); |
| return 0; |
| } |
| } |
| pthread_mutex_unlock(&event_mutex); |
| return 0; |
| } |
| |
| static int check_elapsed_event_from_waitlist(struct timeval *tv, |
| struct timespec *ts) |
| { |
| struct event *event = NULL; |
| int ret; |
| |
| pthread_mutex_lock(&event_mutex); |
| if (!timercmp(tv, &(event_queue->it_interval), >)) { |
| pthread_mutex_unlock(&event_mutex); |
| return 0; |
| } |
| |
| event = event_queue; |
| event_queue= event_queue->next; |
| alarm_triggered_notify(alarm_ctx, event); |
| put_event_to_freelist(event); |
| if (event_queue) { |
| print_event_queue(); |
| TIMEVAL_TO_TIMESPEC(&(event_queue->it_interval), ts); |
| ret = 2; |
| } else { |
| ret = 1; |
| } |
| pthread_mutex_unlock(&event_mutex); |
| return ret; |
| } |
| |
| static int mmp_timer_settime(int alarm_delay, char *name) |
| { |
| struct timespec ts; |
| struct timeval tv; |
| int ret; |
| |
| if (!dev_opened) { |
| afd = open("/dev/alarm", O_RDWR); |
| if (afd < 0) { |
| ERROR("Unable to open rtc: %s\n", strerror(errno)); |
| return 1; |
| } |
| dev_opened = 1; |
| } |
| |
| ret = ioctl(afd, ANDROID_ALARM_GET_TIME(ANDROID_ALARM_RTC_WAKEUP), &ts); |
| if (ret < 0) { |
| ERROR("Unable to get current time: %s\n", strerror(errno)); |
| return 1; |
| } |
| ts.tv_sec += alarm_delay; |
| TIMESPEC_TO_TIMEVAL(&tv, &ts); |
| |
| if (find_event_and_update(name, &tv)) |
| return 0; |
| |
| if (queue_event_to_waitlist(name, &tv)) |
| return 1; |
| |
| return 0; |
| } |
| |
| static int mmp_cancel_timer(char *name) |
| { |
| struct event *event = NULL; |
| struct event *prev; |
| |
| pthread_mutex_lock(&event_mutex); |
| if (!dev_opened || !event_queue) { |
| pthread_mutex_unlock(&event_mutex); |
| return 0; |
| } |
| |
| prev = event = event_queue; |
| while (event) { |
| if (event->alarm_name[0] && |
| !strcmp(name, event->alarm_name)) { |
| DEBUG(2, "mmp_cancel_timer:find alarm_name=%s\n", name); |
| break; |
| } |
| prev = event; |
| event = event->next; |
| } |
| |
| if (!event) { |
| DEBUG(2, "Not found timer: %s\n", name); |
| pthread_mutex_unlock(&event_mutex); |
| return 0; |
| } |
| |
| if (event == event_queue) { |
| event_queue = event_queue->next; |
| print_event_queue(); |
| if (event_queue) |
| alarm_update = 1; |
| if (pthread_kill(alarm_tid, SIGUSR1)) |
| ERROR("%s: pthread_kill failed\n", __func__); |
| } else { |
| prev->next = event->next; |
| } |
| |
| put_event_to_freelist(event); |
| pthread_mutex_unlock(&event_mutex); |
| return 0; |
| } |
| |
| static void check_event_queue() |
| { |
| struct event *event; |
| int i = 0; |
| |
| for (event = event_queue; event; event = event->next) { |
| if (i > g_maxevents) { |
| DEBUG(2, "timer queue is full!"); |
| print_event_queue(); |
| exit(1); |
| } |
| i++; |
| } |
| } |
| |
| static void print_event_queue() |
| { |
| struct event *event; |
| int i = 0; |
| |
| for (event = event_queue; event; event = event->next) { |
| DEBUG(2, "#%d %s: (0x%x)->0x%x: \t%d sec %d usec\n", |
| i++, event->alarm_name ? event->alarm_name : "noname", |
| (unsigned int) event, (unsigned int) event->next, |
| (int) event->it_interval.tv_sec, |
| (int) event->it_interval.tv_usec); |
| if (i > g_maxevents) { |
| DEBUG(2, "...(giving up)\n"); |
| break; |
| } |
| } |
| } |
| |
| static void alarm_sighandler(int signo) |
| { |
| struct timespec ts; |
| int ret; |
| |
| DEBUG(2, "alarm_sighandler: signo=%d\n", signo); |
| if (alarm_update) { |
| alarm_update = 0; |
| TIMEVAL_TO_TIMESPEC(&(event_queue->it_interval), &ts); |
| ret = ioctl(afd, ANDROID_ALARM_SET(ANDROID_ALARM_RTC_WAKEUP), |
| &ts); |
| if (ret < 0) |
| ERROR("Unable to set alarm: %s\n", strerror(errno)); |
| } |
| } |
| |
| static void thread_alarm(void) |
| { |
| struct timespec ts; |
| struct timeval tv; |
| struct sigaction actoins; |
| int ret; |
| |
| pthread_detach(pthread_self()); |
| |
| memset(&actoins, 0, sizeof(actoins)); |
| sigemptyset(&actoins.sa_mask); |
| actoins.sa_flags = 0; |
| actoins.sa_handler = alarm_sighandler; |
| sigaction(SIGUSR1, &actoins, NULL); |
| pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); |
| |
| TIMEVAL_TO_TIMESPEC(&(event_queue->it_interval), &ts); |
| ret = ioctl(afd, ANDROID_ALARM_SET(ANDROID_ALARM_RTC_WAKEUP), &ts); |
| if (ret < 0) { |
| ERROR("Unable to set alarm: %s\n", strerror(errno)); |
| return; |
| } |
| |
| while (true) { |
| ret = ioctl(afd, ANDROID_ALARM_WAIT); |
| if(ret < 0) |
| DEBUG(2, "alarm wait exit due to signal\n"); |
| else |
| DEBUG(2, "got alarm %x\n", ret); |
| if (!event_queue) |
| goto out; |
| |
| ret = ioctl(afd, |
| ANDROID_ALARM_GET_TIME(ANDROID_ALARM_RTC_WAKEUP), &ts); |
| if (ret < 0) |
| ERROR("Unable to get current time: %s\n", |
| strerror(errno)); |
| |
| TIMESPEC_TO_TIMEVAL(&tv, &ts); |
| ret = check_elapsed_event_from_waitlist(&tv, &ts); |
| if (ret == 2) { |
| ret = ioctl(afd, |
| ANDROID_ALARM_SET(ANDROID_ALARM_RTC_WAKEUP), |
| &ts); |
| if (ret < 0) |
| ERROR("Unable to set alarm: %s\n", |
| strerror(errno)); |
| } else if (ret == 1) { |
| /* only 1 event, thread should kill self.*/ |
| goto out; |
| } |
| } |
| out: |
| pthread_exit((void *)0); |
| } |
| |
| int alarm_set(struct ubus_context *ctx, struct ubus_object *obj, |
| struct ubus_request_data *req, const char *method, |
| struct blob_attr *msg) |
| { |
| struct blob_attr *tb[__ALARM_MAX]; |
| |
| int alarm_time; |
| int ret; |
| char *alarm_name = NULL; |
| |
| (void)obj; |
| (void)method; |
| |
| alarm_time = 0; |
| ret = blobmsg_parse(alarm_policy, __ALARM_MAX, tb, blob_data(msg), |
| blob_len(msg)); |
| if (ret) { |
| ERROR("[%s] parsing blobmsg failed %d\n", |
| __FUNCTION__, ret); |
| return -UBUS_STATUS_INVALID_ARGUMENT; |
| } |
| |
| if (tb[ALARM_TIME]) |
| alarm_time = blobmsg_get_u32(tb[ALARM_TIME]); |
| if (tb[ALARM_NAME]) |
| alarm_name = blobmsg_get_string(tb[ALARM_NAME]); |
| |
| DEBUG(2, "---------[%d]-----------\n", alarm_time); |
| if (alarm_time > 0) |
| ret = mmp_timer_settime(alarm_time, alarm_name); |
| else { |
| DEBUG(2, "----error-----[%d]-----------\n", alarm_time); |
| ret = 1; |
| } |
| |
| blobmsg_buf_init(&alarm_buf); |
| blobmsg_add_u32(&alarm_buf, "rc", ret); |
| ubus_send_reply(ctx, req, alarm_buf.head); |
| blob_buf_free(&alarm_buf); |
| return 0; |
| } |
| |
| int alarm_cancel(struct ubus_context *ctx, struct ubus_object *obj, |
| struct ubus_request_data *req, const char *method, |
| struct blob_attr *msg) |
| { |
| struct blob_attr *tb[__ALARM_MAX]; |
| int ret; |
| char *alarm_name = NULL; |
| |
| (void)obj; |
| (void)method; |
| |
| ret = blobmsg_parse(alarm_policy, __ALARM_MAX, tb, blob_data(msg), |
| blob_len(msg)); |
| if (ret) { |
| ERROR("[%s] parsing blobmsg failed %d\n", |
| __FUNCTION__, ret); |
| return UBUS_STATUS_INVALID_ARGUMENT; |
| } |
| |
| if (tb[ALARM_NAME]) { |
| alarm_name = blobmsg_get_string(tb[ALARM_NAME]); |
| ret = mmp_cancel_timer(alarm_name); |
| } |
| |
| blobmsg_buf_init(&alarm_buf); |
| blobmsg_add_u32(&alarm_buf, "rc", ret); |
| ubus_send_reply(ctx, req, alarm_buf.head); |
| blob_buf_free(&alarm_buf); |
| return 0; |
| } |
| |
| void alarm_init(struct ubus_context *ctx) |
| { |
| int i; |
| |
| alarm_ctx = ctx; |
| g_events = 0; |
| dev_opened = 0; |
| g_maxevents = MAX_EVENT; |
| event_freelist = (struct event *) malloc(MAX_EVENT * sizeof(struct event)); |
| memset(event_freelist, 0, MAX_EVENT * sizeof(struct event)); |
| for (i = 0; i < (MAX_EVENT-1); i++) |
| event_freelist[i].next = &event_freelist[i+1]; |
| |
| event_freelist[i].next = NULL; |
| event_queue = NULL; |
| |
| DEBUG(2, "Alarmmanager service running up ...\n"); |
| } |
| |
| #endif //#ifdef CONFIG_ALARM_SERVICE |