blob: 715e9455956eb9605c96cb72b9e423eee5500d1a [file] [log] [blame]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <pthread.h>
#ifdef NAME_MAX
#undef NAME_MAX
#endif
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/netlink.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <fcntl.h>
#include <signal.h>
#include <linux/input.h>
#include <libubox/blob.h>
#include <libubox/blobmsg.h>
#include <libubox/ustream.h>
#include <libubus.h>
#include <ubusmsg.h>
#include "charger.h"
#include "aoc.h"
#ifdef CONFIG_CHARGER_SERVICE
#define POWEROFF_BAT_LEVEL 1
static const char *bat_health_array[] = {
BAT_HEALTH_UNKNOWN,
BAT_HEALTH_GOOD,
BAT_HEALTH_OVERHEAT,
BAT_HEALTH_DEAD,
BAT_HEALTH_OVERVOLTAGE,
BAT_HEALTH_UNSPEC_FAILURE,
BAT_HEALTH_COLD,
BAT_HEALTH_WATCHDOG_TIMER_EXPIRE,
BAT_HEALTH_SAFETY_TIMER_EXPIRE,
};
static const char *bat_tech_array[] = {
BAT_TECHNOLOGY_UNKNOWN,
BAT_TECHNOLOGY_NiMH,
BAT_TECHNOLOGY_LION,
BAT_TECHNOLOGY_LIPO,
BAT_TECHNOLOGY_LiFe,
BAT_TECHNOLOGY_NiCd,
BAT_TECHNOLOGY_LiMn,
};
static const char *bat_type_array[] = {
PSY_TYPE_UNKNOWN,
PSY_TYPE_BATTERY,
PSY_TYPE_UPS,
PSY_TYPE_MAINS,
PSY_TYPE_USB,
PSY_TYPE_USBDCP,
PSY_TYPE_USBCDP,
PSY_TYPE_USBACA,
};
static const char *chgbat_stat_array[] = {
CHGBAT_STATUS_UNKNOWN,
CHGBAT_STATUS_CHARGING,
CHGBAT_STATUS_DISCHARGING,
CHGBAT_STATUS_NOTCHARGING,
CHGBAT_STATUS_FULL,
};
static int should_exit = 0;
static struct chg_bat_stat global_chgbat_info = {0};
static struct chg_bat_tag global_chgbat_array = {0};
static struct ubus_object *charger_object;
static struct blob_buf charger_blob_msg_buf;
struct ubus_context *charger_ctx;
static int read_chgbat_sys_info(void);
#ifdef DEBUG_CHARGER
static void dump_chg_bat_sysinfo(void);
static int read_bat_sys_info(void);
static int read_acchg_sys_info(void);
static int read_usbchg_sys_info(void);
static int read_chg_sys_info();
#endif
int read_chg_bat_info(unsigned int file_mask);
static void convert_chginfo_to_tag(void)
{
unsigned int i;
global_chgbat_array.capacity = global_chgbat_info.capacity;
for(i = 0; i < sizeof(bat_health_array)/sizeof(bat_health_array[0]); i++) {
if(!strncmp((const char *)global_chgbat_info.health,
bat_health_array[i], strlen(bat_health_array[i]))) {
global_chgbat_array.health = i;
break;
}
}
global_chgbat_array.present= global_chgbat_info.present;
for(i = 0; i < sizeof(bat_type_array)/sizeof(bat_type_array[0]); i++) {
if(!strncmp((const char *)global_chgbat_info.type,
bat_type_array[i], strlen(bat_type_array[i]))) {
global_chgbat_array.type = i;
break;
}
}
global_chgbat_array.voltage_now= global_chgbat_info.voltage_now;
for(i = 0; i < sizeof(chgbat_stat_array)/sizeof(chgbat_stat_array[0]); i++) {
if(!strncmp((const char *)global_chgbat_info.bat_status,
chgbat_stat_array[i], strlen(chgbat_stat_array[i]))) {
global_chgbat_array.bat_status = i;
break;
}
}
global_chgbat_array.bat_temp= global_chgbat_info.bat_temp;
for(i = 0; i < sizeof(bat_tech_array)/sizeof(bat_tech_array[0]); i++) {
if(!strncmp((const char *)global_chgbat_info.bat_technology,
bat_tech_array[i], strlen(bat_tech_array[i]))) {
global_chgbat_array.bat_technology = i;
break;
}
}
global_chgbat_array.acchg_online= global_chgbat_info.acchg_online;
for(i = 0; i < sizeof(bat_type_array)/sizeof(bat_type_array[0]); i++) {
if(!strncmp((const char *)global_chgbat_info.acchg_type,
bat_type_array[i], strlen(bat_type_array[i]))) {
global_chgbat_array.acchg_type = i;
break;
}
}
for(i = 0; i < sizeof(chgbat_stat_array)/sizeof(chgbat_stat_array[0]); i++) {
if(!strncmp((const char *)global_chgbat_info.acchg_status,
chgbat_stat_array[i], strlen(chgbat_stat_array[i]))) {
global_chgbat_array.acchg_status = i;
break;
}
}
global_chgbat_array.usbchg_online= global_chgbat_info.usbchg_online;
for(i = 0; i < sizeof(bat_type_array)/sizeof(bat_type_array[0]); i++) {
if(!strncmp((const char *)global_chgbat_info.usbchg_type,
bat_type_array[i], strlen(bat_type_array[i]))) {
global_chgbat_array.usbchg_type= i;
break;
}
}
for(i = 0; i < sizeof(chgbat_stat_array)/sizeof(chgbat_stat_array[0]); i++) {
if(!strncmp((const char *)global_chgbat_info.usbchg_status,
chgbat_stat_array[i], strlen(chgbat_stat_array[i]))) {
global_chgbat_array.usbchg_status= i;
break;
}
}
}
static void prepare_chgbat_info(int sysfs_ok)
{
if(!sysfs_ok)
ERROR("%s: sysfs_ok = 0\n", __func__);
convert_chginfo_to_tag();
blob_buf_init(&charger_blob_msg_buf, 0);
blobmsg_add_u32(&charger_blob_msg_buf, "capacity", global_chgbat_array.capacity);
blobmsg_add_u32(&charger_blob_msg_buf, "health", global_chgbat_array.health);
blobmsg_add_u32(&charger_blob_msg_buf, "present", global_chgbat_array.present);
blobmsg_add_u32(&charger_blob_msg_buf, "type", global_chgbat_array.type);
blobmsg_add_u32(&charger_blob_msg_buf, "voltage_now", global_chgbat_array.voltage_now);
blobmsg_add_u32(&charger_blob_msg_buf, "bat_status", global_chgbat_array.bat_status);
blobmsg_add_u32(&charger_blob_msg_buf, "bat_temp", global_chgbat_array.bat_temp);
blobmsg_add_u32(&charger_blob_msg_buf, "bat_technology", global_chgbat_array.bat_technology);
blobmsg_add_u32(&charger_blob_msg_buf, "acchg_online", global_chgbat_array.acchg_online);
blobmsg_add_u32(&charger_blob_msg_buf, "acchg_type", global_chgbat_array.acchg_type);
blobmsg_add_u32(&charger_blob_msg_buf, "acchg_status", global_chgbat_array.acchg_status);
blobmsg_add_u32(&charger_blob_msg_buf, "usbchg_online", global_chgbat_array.usbchg_online);
blobmsg_add_u32(&charger_blob_msg_buf, "usbchg_type", global_chgbat_array.usbchg_type);
blobmsg_add_u32(&charger_blob_msg_buf, "usbchg_status", global_chgbat_array.usbchg_status);
}
#ifdef DEBUG_CHARGER
static void dump_chg_bat_info(void)
{
DEBUG(1, "\nBattery capacity: %d/100\n",global_chgbat_info.capacity);
DEBUG(1, " Battery type: %s\n",global_chgbat_info.type);
DEBUG(1, " Battery present: %d\n",global_chgbat_info.present);
DEBUG(1, " Battery health: %s\n",global_chgbat_info.health);
DEBUG(1, " Battery voltage: %d\n",global_chgbat_info.voltage_now);
DEBUG(1, " Battery temp: %d\n",global_chgbat_info.bat_temp);
DEBUG(1, " Battery status: %s\n",global_chgbat_info.bat_status);
DEBUG(1, " Battery tech: %s\n",global_chgbat_info.bat_technology);
DEBUG(1, " Acchg online: %d\n",global_chgbat_info.acchg_online);
DEBUG(1, " Acchg type: %s\n",global_chgbat_info.acchg_type);
DEBUG(1, " Acchg status: %s\n",global_chgbat_info.acchg_status);
DEBUG(1, " USBchg online: %d\n",global_chgbat_info.usbchg_online);
DEBUG(1, " USBchg type: %s\n",global_chgbat_info.usbchg_type);
DEBUG(1, " USBchg status: %s\n",global_chgbat_info.usbchg_status);
}
#endif
int send_chg_msg(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
(void)obj;
(void)method;
(void)msg;
DEBUG(2, "[%s]\n", __FUNCTION__);
pthread_mutex_lock(&aoc_ubus_mutex);
ubus_send_reply(ctx, req, charger_blob_msg_buf.head);
pthread_mutex_unlock(&aoc_ubus_mutex);
DEBUG(2, "[%s] ubus_send_reply OK\n", __FUNCTION__);
return UBUS_STATUS_OK;
}
extern void poweroff_system(void);
static void handle_lowbat_poweroff(void)
{
if(global_chgbat_array.capacity <= POWEROFF_BAT_LEVEL
&& global_chgbat_array.bat_status != STATUS_CHARGING) {
LOG("%s: power off, battery too low\n", __FUNCTION__);
poweroff_system();
}
}
static void handle_tbat_poweroff(void)
{
if(global_chgbat_array.health == HEALTH_DEAD) {
LOG("%s: power off, BAT Temperature Abnormal\n", __FUNCTION__);
poweroff_system();
}
}
static int notify_chg_msg(int event_type)
{
int ret = 0;
char ev_type[16] = "\0";
if(event_type == BAT_EVENT)
strcpy(ev_type, "bat_event");
else if(event_type == ACCHG_EVENT)
strcpy(ev_type, "acchg_event");
else if(event_type == USBCHG_EVENT)
strcpy(ev_type, "usbchg_event");
ret = read_chgbat_sys_info();
if(ret) {
ERROR("%s: read chgbat info error\n", __FUNCTION__);
/* return -UBUS_STATUS_NO_DATA; */
}
if(ret)
prepare_chgbat_info(0);
else
prepare_chgbat_info(1);
handle_tbat_poweroff();
handle_lowbat_poweroff();
pthread_mutex_lock(&aoc_ubus_mutex);
ret = ubus_notify(charger_ctx, charger_object, "chginfo",
charger_blob_msg_buf.head, -1);
pthread_mutex_unlock(&aoc_ubus_mutex);
if (ret)
ERROR("%s: ubus notify error: ret=%d\n", __FUNCTION__, ret);
return ret;
}
static int
file_check_and_open(struct chg_file_desc * file_desc,
struct chg_bat_stat * chg_bat_status)
{
int ret = 0;
if(!file_desc || !chg_bat_status) {
ERROR("%s: %d: file_desc or chg_bat_status NULL\n", __func__, __LINE__);
ret = -EINVAL;
goto out_err;
}
if(!file_desc->file_name && (file_desc->fd == -1)) {
ERROR("%s: %d: file name NULL\n", __func__, __LINE__);
ret = -EINVAL;
goto out_err;
}
file_desc->fd = open(file_desc->file_name, O_RDONLY);
if(file_desc->fd < 0) {
ERROR("[%s] file-%s open error\n", __FUNCTION__, file_desc->file_name);
ret = -ENOENT;
goto out_err;
}
return 0;
out_err:
return ret;
}
static void close_chg_file(struct chg_file_desc * file_desc)
{
close(file_desc->fd);
file_desc->fd = -1;
}
static int
read_bat_files(struct chg_file_desc * file_desc,
struct chg_bat_stat * chg_bat_status, int attrib_idx)
{
int ret = 0;
unsigned char str[CHGBAT_STR_NAME_LEN];
unsigned char *str_temp = NULL;
int * p_value = 0x0;
ret = file_check_and_open(file_desc, chg_bat_status);
if(ret) {
ERROR("%s: %d: file check and open error\n", __func__, __LINE__);
goto out_err;
}
switch(attrib_idx)
{
case BAT_CAPACITY_FILE_IDX:
str_temp = str;
p_value = (int *)&(chg_bat_status->capacity);
break;
case BAT_HEALTH_FILE_IDX:
str_temp = chg_bat_status->health;
break;
case BAT_PRESENT_FILE_IDX:
str_temp = str;
p_value = (int *)&(chg_bat_status->present);
break;
case BAT_TEMP_FILE_IDX:
str_temp = str;
p_value = (int *)&(chg_bat_status->bat_temp);
break;
case BAT_STATUS_FILE_IDX:
str_temp = chg_bat_status->bat_status;
break;
case BAT_TECH_FILE_IDX:
str_temp = chg_bat_status->bat_technology;
break;
case BAT_TYPE_FILE_IDX:
str_temp = chg_bat_status->type;
break;
case BAT_VOLT_FILE_IDX:
str_temp = str;
p_value = (int *)&(chg_bat_status->voltage_now);
break;
case ACCHG_ONLINE_FILE_IDX:
str_temp = str;
p_value = (int *)&(chg_bat_status->acchg_online);
break;
case ACCHG_TYPE_FILE_IDX:
str_temp = chg_bat_status->acchg_type;
break;
case ACCHG_STATUS_FILE_IDX:
str_temp = chg_bat_status->acchg_status;
break;
case USBCHG_ONLINE_FILE_IDX:
str_temp = str;
p_value = (int *)&(chg_bat_status->usbchg_online);
break;
case USBCHG_TYPE_FILE_IDX:
str_temp = chg_bat_status->usbchg_type;
break;
case USBCHG_STATUS_FILE_IDX:
str_temp = chg_bat_status->usbchg_status;
break;
default:
ERROR("[%s]: attribute_idx %d error\n", __func__, attrib_idx);
return -1;
}
ret = read(file_desc->fd, str_temp, CHGBAT_STR_NAME_LEN);
if(ret < 0) {
ERROR("[%s] file read error\n", __FUNCTION__);
close_chg_file(file_desc);
goto out_err;
}
if(p_value)
*p_value = atoi((const char *)str_temp);
close_chg_file(file_desc);
return 0;
out_err:
return ret;
}
static struct chg_file_desc chg_files[] = {
[BAT_CAPACITY_FILE_IDX] = {
.fd = -1,
.file_name = BAT_CAPACITY_FILE,
.read_file = &read_bat_files,
},
[BAT_HEALTH_FILE_IDX] = {
.fd = -1,
.file_name = BAT_HEALTH_FILE,
.read_file = &read_bat_files,
},
[BAT_PRESENT_FILE_IDX] = {
.fd = -1,
.file_name = BAT_PRESENT_FILE,
.read_file = &read_bat_files,
},
[BAT_TEMP_FILE_IDX] = {
.fd = -1,
.file_name = BAT_TEMP_FILE,
.read_file = &read_bat_files,
},
[BAT_STATUS_FILE_IDX] = {
.fd = -1,
.file_name = BAT_STATUS_FILE,
.read_file = &read_bat_files,
},
[BAT_TECH_FILE_IDX] = {
.fd = -1,
.file_name = BAT_TECH_FILE,
.read_file = &read_bat_files,
},
[BAT_TYPE_FILE_IDX] = {
.fd = -1,
.file_name = BAT_TYPE_FILE,
.read_file = &read_bat_files,
},
[BAT_VOLT_FILE_IDX] = {
.fd = -1,
.file_name = BAT_VOLT_FILE,
.read_file = &read_bat_files,
},
[ACCHG_ONLINE_FILE_IDX] = {
.fd = -1,
.file_name = ACCHG_ONLINE_FILE,
.read_file = &read_bat_files,
},
[ACCHG_TYPE_FILE_IDX] = {
.fd = -1,
.file_name = ACCHG_TYPE_FILE,
.read_file = &read_bat_files,
},
[ACCHG_STATUS_FILE_IDX] = {
.fd = -1,
.file_name = ACCHG_STATUS_FILE,
.read_file = &read_bat_files,
},
[USBCHG_ONLINE_FILE_IDX] = {
.fd = -1,
.file_name = USBCHG_ONLINE_FILE,
.read_file = &read_bat_files,
},
[USBCHG_TYPE_FILE_IDX] = {
.fd = -1,
.file_name = USBCHG_TYPE_FILE,
.read_file = &read_bat_files,
},
[USBCHG_STATUS_FILE_IDX] = {
.fd = -1,
.file_name = USBCHG_STATUS_FILE,
.read_file = &read_bat_files,
},
};
int read_chg_bat_info(unsigned int file_mask)
{
int i, ret = 0;
for(i = 0; i < NR_CHG_USB_FILE_IDX; i++)
{
if((0x1 << i) & file_mask) {
ret |= chg_files[i].read_file(&chg_files[i], &global_chgbat_info, i);
if(ret)
break;
}
}
return ret;
}
static int read_chgbat_sys_info(void)
{
int ret;
ret = read_chg_bat_info(ALL_CHG_BAT_FILES);
return ret;
}
#ifdef DEBUG_CHARGER
static int read_bat_sys_info(void)
{
int ret;
ret = read_chg_bat_info(ALL_BAT_FILES);
return ret;
}
static int read_acchg_sys_info(void)
{
int ret;
ret = read_chg_bat_info(ALL_AC_CHG_FILES);
return ret;
}
static int read_usbchg_sys_info(void)
{
int ret;
ret = read_chg_bat_info(ALL_USB_CHG_FILES);
return ret;
}
static int read_chg_sys_info(void)
{
int ret;
ret = read_chg_bat_info(ALL_USB_CHG_FILES|ALL_AC_CHG_FILES);
return ret;
}
#endif
#ifdef DEBUG_CHARGER
static void dump_chg_bat_sysinfo(void)
{
int ret;
ret = read_chgbat_sys_info();
if(ret) {
ERROR("[%s] read chg bat info error\n", __FUNCTION__);
return;
}
dump_chg_bat_info();
}
#endif
static int handle_battery_event(void)
{
notify_chg_msg(BAT_EVENT);
return 0;
}
static int handle_acchg_event(void)
{
notify_chg_msg(ACCHG_EVENT);
return 0;
}
static int handle_usbchg_event(void)
{
notify_chg_msg(USBCHG_EVENT);
return 0;
}
static int get_chgevent_to_deliver(int uevent_sock)
{
char uevent_buf[UEVENT_BUFFER_SIZE * 2] = {0};
while(!should_exit)
{
memset(uevent_buf, 0x0, sizeof(uevent_buf));
recv(uevent_sock, &uevent_buf, sizeof(uevent_buf), 0);
uevent_buf[sizeof(uevent_buf) - 1] = 0x0;
if(strstr(uevent_buf,"power_supply/bat"))
{
DEBUG(2, "recevied battery event\n");
handle_battery_event();
}
else if (strstr(uevent_buf,"power_supply/ac"))
{
DEBUG(2, "recevied ac charger event\n");
handle_acchg_event();
}
else if(strstr(uevent_buf,"power_supply/usb"))
{
DEBUG(2, "recevied usb charger event\n");
handle_usbchg_event();
}
}
close(uevent_sock);
return 0;
}
static int init_uevent_sock(void)
{
const int buffersize = UEVENT_BUFFER_SIZE;
int ret, sock_fd;
struct sockaddr_nl snl;
bzero(&snl, sizeof(struct sockaddr_nl));
snl.nl_family = AF_NETLINK;
snl.nl_pid = getpid();
snl.nl_groups = 1;
sock_fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
if (sock_fd < 0)
{
ERROR("[%s] socket error\n", __FUNCTION__);
return -1;
}
setsockopt(sock_fd, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize));
ret = bind(sock_fd, (struct sockaddr *)&snl, sizeof(struct sockaddr_nl));
if (ret < 0)
{
ERROR("%s: bind error\n", __FUNCTION__);
close(sock_fd);
return -1;
}
return sock_fd;
}
static void chgevent_mon_thread_handler(void *arg)
{
int sock_fd;
(void)arg;
sock_fd = init_uevent_sock();
if(sock_fd == -1)
goto out_err;
get_chgevent_to_deliver(sock_fd);
return;
out_err:
close(sock_fd);
return;
}
static pthread_t create_chgevent_mon_thread(void * arg)
{
int ret;
pthread_t chgevent_mon_thread;
DEBUG(2, "%s\n",__func__);
ret = pthread_create(&chgevent_mon_thread, NULL,
(void *)chgevent_mon_thread_handler, arg);
if(ret) {
ERROR("[%s] pthread_create error\n", __FUNCTION__);
return (pthread_t)0;
}
return chgevent_mon_thread;
}
static void sigkill_func(int sig)
{
DEBUG(2, "received sig: %d\n",sig);
should_exit = 1;
if(sig == SIGSEGV) {
exit(1);
}
}
int charger_init(struct ubus_context *ctx, struct ubus_object *obj)
{
int ret;
pthread_t chgevent_mon_thread;
DEBUG(2, "dump charger battery info\n");
/* check if the charger driver is there */
ret = read_chgbat_sys_info();
if(ret) {
ERROR("kernel charger driver not loaded\n");
prepare_chgbat_info(0);
} else {
DEBUG(2, "Kernel charger driver loaded\n");
prepare_chgbat_info(1);
}
charger_ctx = ctx;
charger_object = obj;
chgevent_mon_thread = create_chgevent_mon_thread(NULL);
if (!chgevent_mon_thread) {
DEBUG(2, "create chgevent monitor thread\n");
return -1;
}
DEBUG(2, "charger service running up ...\n");
signal(SIGTERM, sigkill_func);
signal(SIGKILL, sigkill_func);
signal(SIGSEGV, sigkill_func);
return 0;
}
#endif //#ifdef CONFIG_CHARGER_SERVICE