494 lines
11 KiB
C++
Executable File
494 lines
11 KiB
C++
Executable File
/*
|
|
* Copyright (C) 2013 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define TAG "healthd"
|
|
#include <tina_log.h>
|
|
|
|
#include "healthd.h"
|
|
#include "BatteryMonitor.h"
|
|
|
|
#include <libgen.h>
|
|
#include <linux/input.h>
|
|
#include <pthread.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/timerfd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <linux/input.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
#include <poll.h>
|
|
#include <dirent.h>
|
|
#include <sys/socket.h>
|
|
#include <linux/netlink.h>
|
|
#include <sys/time.h>
|
|
#include <signal.h>
|
|
|
|
using namespace softwinner;
|
|
|
|
// Periodic chores intervals in seconds
|
|
#define DEFAULT_PERIODIC_CHORES_INTERVAL_FAST (60)
|
|
#define DEFAULT_PERIODIC_CHORES_INTERVAL_SLOW (60*3)
|
|
|
|
#define POWER_SUPPLY_SUBSYSTEM "power_supply"
|
|
|
|
static struct healthd_config healthd_config = {
|
|
.periodic_chores_interval_fast = DEFAULT_PERIODIC_CHORES_INTERVAL_FAST,
|
|
.periodic_chores_interval_slow = DEFAULT_PERIODIC_CHORES_INTERVAL_SLOW,
|
|
.batteryPresent = -1,
|
|
.batteryCapacity = -1,
|
|
.batteryVoltage = -1,
|
|
.batteryTemperature = -1,
|
|
.batteryCurrentNow = -1,
|
|
.batteryStatus = -1,
|
|
.batteryHealth = -1,
|
|
.screen_on = NULL,
|
|
|
|
};
|
|
|
|
static int wakealarm_wake_interval = DEFAULT_PERIODIC_CHORES_INTERVAL_FAST;
|
|
|
|
static int eventct;
|
|
static int epollfd;
|
|
|
|
#define MAX_EPOLL_EVENTS 40
|
|
static int uevent_fd;
|
|
static int wakealarm_fd;
|
|
|
|
static int is_charger = 0;
|
|
static int awake_poll_interval = -1;
|
|
|
|
struct healthd_mode_ops *healthd_mode_ops;
|
|
|
|
/* Tina mode */
|
|
extern void healthd_mode_tina_init(struct healthd_config *config);
|
|
extern int healthd_mode_tina_preparetowait(void);
|
|
extern void healthd_mode_tina_battery_update(struct BatteryProperties *props);
|
|
|
|
/* Charger mode */
|
|
extern void healthd_mode_charger_init(struct healthd_config *config);
|
|
extern int healthd_mode_charger_preparetowait(void);
|
|
extern void healthd_mode_charger_heartbeat(void);
|
|
extern void healthd_mode_charger_battery_update(struct BatteryProperties *props);
|
|
|
|
/* NOPs for modes that need no special action */
|
|
static void healthd_mode_nop_init(struct healthd_config *config);
|
|
static int healthd_mode_nop_preparetowait(void);
|
|
static void healthd_mode_nop_heartbeat(void);
|
|
static void healthd_mode_nop_battery_update(struct BatteryProperties *props);
|
|
|
|
static struct healthd_mode_ops tina_ops = {
|
|
.init = healthd_mode_tina_init,
|
|
.preparetowait = healthd_mode_tina_preparetowait,
|
|
.heartbeat = healthd_mode_nop_heartbeat,
|
|
.battery_update = healthd_mode_tina_battery_update,
|
|
};
|
|
|
|
static struct healthd_mode_ops charger_ops = {
|
|
#ifdef SHUTDOWN_CHARGER
|
|
.init = healthd_mode_charger_init,
|
|
.preparetowait = healthd_mode_charger_preparetowait,
|
|
.heartbeat = healthd_mode_charger_heartbeat,
|
|
.battery_update = healthd_mode_charger_battery_update,
|
|
#else
|
|
.init = NULL,
|
|
.preparetowait = NULL,
|
|
.heartbeat = NULL,
|
|
.battery_update = NULL,
|
|
#endif
|
|
};
|
|
|
|
static struct healthd_mode_ops recovery_ops = {
|
|
.init = healthd_mode_nop_init,
|
|
.preparetowait = healthd_mode_nop_preparetowait,
|
|
.heartbeat = healthd_mode_nop_heartbeat,
|
|
.battery_update = healthd_mode_nop_battery_update,
|
|
};
|
|
|
|
static void healthd_mode_nop_init(struct healthd_config * /*config */ )
|
|
{
|
|
}
|
|
|
|
static int healthd_mode_nop_preparetowait(void)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
static void healthd_mode_nop_heartbeat(void)
|
|
{
|
|
}
|
|
|
|
static void healthd_mode_nop_battery_update(struct BatteryProperties * /*props */ )
|
|
{
|
|
}
|
|
|
|
static BatteryMonitor *gBatteryMonitor;
|
|
|
|
#define PROP_NAME_MAX 256
|
|
|
|
int healthd_register_event(int fd, void (*handler) (uint32_tt))
|
|
{
|
|
struct epoll_event ev;
|
|
|
|
ev.events = EPOLLIN;
|
|
if (is_charger)
|
|
ev.events |= EPOLLWAKEUP;
|
|
ev.data.ptr = (void *)handler;
|
|
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
|
|
TLOGE("epoll_ctl failed; errno=%d\n", errno);
|
|
return -1;
|
|
}
|
|
|
|
eventct++;
|
|
return 0;
|
|
}
|
|
|
|
static void wakealarm_set_interval(int interval)
|
|
{
|
|
struct itimerspec itval;
|
|
|
|
if (wakealarm_fd == -1)
|
|
return;
|
|
|
|
wakealarm_wake_interval = interval;
|
|
|
|
if (interval == -1)
|
|
interval = 0;
|
|
|
|
itval.it_interval.tv_sec = interval;
|
|
itval.it_interval.tv_nsec = 0;
|
|
itval.it_value.tv_sec = interval;
|
|
itval.it_value.tv_nsec = 0;
|
|
|
|
if (timerfd_settime(wakealarm_fd, 0, &itval, NULL) == -1)
|
|
TLOGE("wakealarm_set_interval: timerfd_settime failed\n");
|
|
}
|
|
|
|
static void import_kernel_nv(char *name, int for_emulator)
|
|
{
|
|
char *value = strchr(name, '=');
|
|
int name_len = strlen(name);
|
|
|
|
if (value == 0)
|
|
return;
|
|
*value++ = 0;
|
|
if (name_len == 0)
|
|
return;
|
|
|
|
if (!strncmp(name, "boot.mode", 9) && name_len > 9) {
|
|
const char *boot_prop_name = name + 10;
|
|
DLOG("boot_prop_name=%s\n", boot_prop_name);
|
|
|
|
if (!strcmp(boot_prop_name, "charger")) {
|
|
is_charger = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void import_kernel_cmdline(int in_qemu, void (*import_kernel_nv) (char *name, int in_qemu))
|
|
{
|
|
char cmdline[1024];
|
|
char *ptr;
|
|
int fd;
|
|
|
|
fd = open("/proc/cmdline", O_RDONLY);
|
|
if (fd >= 0) {
|
|
int n = read(fd, cmdline, 1023);
|
|
if (n < 0)
|
|
n = 0;
|
|
/* get rid of trailing newline, it happens */
|
|
if (n > 0 && cmdline[n - 1] == '\n')
|
|
n--;
|
|
cmdline[n] = 0;
|
|
close(fd);
|
|
} else {
|
|
cmdline[0] = 0;
|
|
}
|
|
ptr = cmdline;
|
|
while (ptr && *ptr) {
|
|
char *x = strchr(ptr, ' ');
|
|
if (x != 0)
|
|
*x++ = 0;
|
|
import_kernel_nv(ptr, in_qemu);
|
|
ptr = x;
|
|
}
|
|
}
|
|
|
|
static void process_kernel_cmdline(void)
|
|
{
|
|
chmod("/proc/cmdline", 0440);
|
|
import_kernel_cmdline(0, import_kernel_nv);
|
|
}
|
|
|
|
int uevent_open_socket(int buf_sz, bool passcred)
|
|
{
|
|
|
|
struct sockaddr_nl addr;
|
|
int on = passcred;
|
|
int s;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.nl_family = AF_NETLINK;
|
|
addr.nl_pid = getpid();
|
|
addr.nl_groups = 0xffffffff;
|
|
s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
|
|
if (s < 0)
|
|
return -1;
|
|
|
|
setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &buf_sz, sizeof(buf_sz));
|
|
setsockopt(s, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
|
|
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
close(s);
|
|
return -1;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
ssize_t uevent_kernel_multicast_uid_recv(int socket, void *buffer, size_t length, uid_t * user)
|
|
{
|
|
struct iovec iov = { buffer, length };
|
|
struct sockaddr_nl addr;
|
|
char control[CMSG_SPACE(sizeof(struct ucred))];
|
|
struct msghdr hdr = {
|
|
&addr,
|
|
sizeof(addr),
|
|
&iov,
|
|
1,
|
|
};
|
|
hdr.msg_control = (void *)control;
|
|
hdr.msg_controllen = sizeof(control);
|
|
*user = -1;
|
|
ssize_t n = recvmsg(socket, &hdr, 0);
|
|
if (n <= 0) {
|
|
return n;
|
|
}
|
|
|
|
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr);
|
|
if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
|
|
bzero(buffer, length);
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
struct ucred *cred = (struct ucred *)CMSG_DATA(cmsg);
|
|
*user = cred->uid;
|
|
if (cred->uid != 0) {
|
|
bzero(buffer, length);
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
if (addr.nl_groups == 0 || addr.nl_pid != 0) {
|
|
bzero(buffer, length);
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
ssize_t uevent_kernel_multicast_recv(int socket, void *buffer, size_t length)
|
|
{
|
|
uid_t user = -1;
|
|
return uevent_kernel_multicast_uid_recv(socket, buffer, length, &user);
|
|
}
|
|
|
|
void healthd_battery_update(void)
|
|
{
|
|
int new_wake_interval = gBatteryMonitor->update()?
|
|
healthd_config.periodic_chores_interval_fast : healthd_config.periodic_chores_interval_slow;
|
|
|
|
if (new_wake_interval != wakealarm_wake_interval)
|
|
wakealarm_set_interval(new_wake_interval);
|
|
if (healthd_config.periodic_chores_interval_fast == -1)
|
|
awake_poll_interval = -1;
|
|
else
|
|
awake_poll_interval =
|
|
new_wake_interval == healthd_config.periodic_chores_interval_fast ?
|
|
-1 : healthd_config.periodic_chores_interval_fast * 1000;
|
|
}
|
|
|
|
static void periodic_chores()
|
|
{
|
|
healthd_battery_update();
|
|
}
|
|
|
|
#define UEVENT_MSG_LEN 2048
|
|
static void uevent_event(uint32_tt)
|
|
{
|
|
char msg[UEVENT_MSG_LEN + 2];
|
|
char *cp;
|
|
int n;
|
|
|
|
n = uevent_kernel_multicast_recv(uevent_fd, msg, UEVENT_MSG_LEN);
|
|
if (n <= 0)
|
|
return;
|
|
if (n >= UEVENT_MSG_LEN) /* overflow -- discard */
|
|
return;
|
|
msg[n] = '\0';
|
|
msg[n + 1] = '\0';
|
|
cp = msg;
|
|
|
|
while (*cp) {
|
|
if (!strcmp(cp, "SUBSYSTEM=" POWER_SUPPLY_SUBSYSTEM)) {
|
|
healthd_battery_update();
|
|
break;
|
|
}
|
|
while (*cp++) ;
|
|
}
|
|
}
|
|
|
|
static void uevent_init(void)
|
|
{
|
|
uevent_fd = uevent_open_socket(64 * 1024, true);
|
|
|
|
if (uevent_fd < 0) {
|
|
TLOGE("uevent_init: uevent_open_socket failed\n");
|
|
return;
|
|
}
|
|
fcntl(uevent_fd, F_SETFL, O_NONBLOCK);
|
|
if (healthd_register_event(uevent_fd, uevent_event))
|
|
TLOGE("register for uevent events failed\n");
|
|
|
|
}
|
|
|
|
static const char *pwr_state_mem = "mem";
|
|
static int set_power_state_mem(void)
|
|
{
|
|
const int SIZE = 256;
|
|
char file[256];
|
|
int ret;
|
|
sprintf(file, "sys/power/%s", "state");
|
|
int fd = open(file, O_RDWR, 0);
|
|
if (fd == -1) {
|
|
TLOGE("Could not open '%s'\n", file);
|
|
return -1;
|
|
}
|
|
ret = write(fd, pwr_state_mem, strlen(pwr_state_mem));
|
|
if (ret < 0) {
|
|
TLOGE("set_power_state_mem err\n");
|
|
}
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
static void wakealarm_event(uint32_tt /*epevents */ )
|
|
{
|
|
unsigned long long wakeups;
|
|
|
|
DLOG("enter\n");
|
|
if (read(wakealarm_fd, &wakeups, sizeof(wakeups)) == -1) {
|
|
TLOGE("wakealarm_event: read wakealarm fd failed\n");
|
|
return;
|
|
}
|
|
periodic_chores();
|
|
}
|
|
|
|
static void wakealarm_init(void)
|
|
{
|
|
/* change CLOCK_BOOTTIME_ALARM to CLOCK_MONOTONIC, avoid wakeup frequently! */
|
|
wakealarm_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
|
|
if (wakealarm_fd == -1) {
|
|
TLOGE("wakealarm_init: timerfd_create failed\n");
|
|
return;
|
|
}
|
|
|
|
if (healthd_register_event(wakealarm_fd, wakealarm_event))
|
|
TLOGE("Registration of wakealarm event failed\n");
|
|
|
|
wakealarm_set_interval(healthd_config.periodic_chores_interval_fast);
|
|
}
|
|
|
|
static void healthd_mainloop(void)
|
|
{
|
|
while (1) {
|
|
struct epoll_event events[eventct];
|
|
int nevents;
|
|
int timeout = awake_poll_interval;
|
|
int mode_timeout;
|
|
mode_timeout = healthd_mode_ops->preparetowait();
|
|
DLOG("mode_timeout=%d\n", mode_timeout);
|
|
if (timeout < 0 || (mode_timeout > 0 && mode_timeout < timeout))
|
|
timeout = mode_timeout;
|
|
nevents = epoll_wait(epollfd, events, eventct, timeout);
|
|
if (nevents == -1) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
TLOGE("healthd_mainloop: epoll_wait failed\n");
|
|
break;
|
|
}
|
|
|
|
for (int n = 0; n < nevents; ++n) {
|
|
if (events[n].data.ptr)
|
|
(*(void (*)(int))events[n].data.ptr) (events[n].events);
|
|
}
|
|
if (!nevents) {
|
|
periodic_chores();
|
|
}
|
|
healthd_mode_ops->heartbeat();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
static int healthd_init()
|
|
{
|
|
epollfd = epoll_create(MAX_EPOLL_EVENTS);
|
|
if (epollfd == -1) {
|
|
TLOGE("epoll_create failed; errno=%d\n", errno);
|
|
return -1;
|
|
}
|
|
|
|
healthd_mode_ops->init(&healthd_config);
|
|
wakealarm_init();
|
|
uevent_init();
|
|
gBatteryMonitor = new BatteryMonitor();
|
|
gBatteryMonitor->init(&healthd_config, &is_charger);
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int ret;
|
|
|
|
/* tina_ops used to broadcast battery info */
|
|
healthd_mode_ops = &tina_ops;
|
|
#if 1
|
|
if(charger_ops.init) {
|
|
process_kernel_cmdline();
|
|
if (is_charger)
|
|
healthd_mode_ops = &charger_ops;
|
|
}
|
|
#else
|
|
healthd_mode_ops = &charger_ops;
|
|
#endif
|
|
ret = healthd_init();
|
|
if (ret) {
|
|
TLOGE("Initialization failed, exiting\n");
|
|
exit(2);
|
|
}
|
|
|
|
healthd_mainloop();
|
|
TLOGE("Main loop terminated, exiting\n");
|
|
return 0;
|
|
}
|