0.1 release
[pandora_liveinfo.git] / main.c
CommitLineData
063ae4be 1/*
2 * pandora live info
3 * (C) notaz, 2014
4 *
5 * This work is licensed under the terms of 3-clause BSD license.
6 * See COPYING file in the top-level directory.
7 */
8
9#include <stdio.h>
10#include <string.h>
11#include <stdarg.h>
12#include <pthread.h>
13#include <sys/types.h>
14#include <sys/stat.h>
15#include <fcntl.h>
16#include <sys/ioctl.h>
17#include <sys/mman.h>
18#include <unistd.h>
19#include <sys/time.h>
20#include <time.h>
21#include <sys/socket.h>
22#include <sys/un.h>
23#include <signal.h>
24#include <poll.h>
25#include <linux/fb.h>
26#include <linux/omapfb.h>
27#include <stdint.h>
28#include "fonts.h"
29
30#define SCREEN_WIDTH 800
31#define SCREEN_HEIGHT 480
32#define WIDTH 80
33#define HEIGHT 480
34#define MEM_SIZE (((WIDTH * HEIGHT * 2 * 2) + 0xfff) & ~0xfff)
35#define Y_STEP 9
36
37static struct fb_var_screeninfo g_vi;
38static uint16_t *g_screen_base, *g_screen;
39static unsigned int g_old_mem;
40static int g_fd, g_exit;
41
42#define IS_START(buf, str) \
43 !strncmp(buf, str, sizeof(str) - 1)
44
45static int setup_layer(void)
46{
47 static const char fbname[] = "/dev/fb2";
48 struct omapfb_plane_info pi;
49 struct omapfb_mem_info mi;
50 struct omapfb_color_key key;
51 int ret;
52
53 g_fd = open(fbname, O_RDWR);
54 if (g_fd == -1) {
55 fprintf(stderr, "open %s: ", fbname);
56 perror(NULL);
57 return 1;
58 }
59
60 ret = ioctl(g_fd, OMAPFB_QUERY_PLANE, &pi);
61 if (ret < 0) {
62 perror("ioctl OMAPFB_QUERY_PLANE");
63 return 1;
64 }
65
66 ret = ioctl(g_fd, OMAPFB_QUERY_MEM, &mi);
67 if (ret < 0) {
68 perror("ioctl OMAPFB_QUERY_MEM");
69 return 1;
70 }
71
72 /* must disable when changing stuff */
73 if (pi.enabled) {
74 pi.enabled = 0;
75 ret = ioctl(g_fd, OMAPFB_SETUP_PLANE, &pi);
76 if (ret < 0) {
77 perror("ioctl OMAPFB_SETUP_PLANE (d)");
78 return 1;
79 }
80 }
81
82 g_old_mem = mi.size;
83 if (mi.size < MEM_SIZE) {
84 mi.size = MEM_SIZE;
85 ret = ioctl(g_fd, OMAPFB_SETUP_MEM, &mi);
86 if (ret < 0) {
87 perror("ioctl SETUP_MEM");
88 return 1;
89 }
90 }
91
92 pi.pos_x = SCREEN_WIDTH - WIDTH;
93 pi.pos_y = 0;
94 pi.out_width = WIDTH;
95 pi.out_height = HEIGHT;
96 ret = ioctl(g_fd, OMAPFB_SETUP_PLANE, &pi);
97 if (ret < 0) {
98 perror("ioctl OMAPFB_SETUP_PLANE (e1)");
99 return 1;
100 }
101
102 ret = ioctl(g_fd, FBIOGET_VSCREENINFO, &g_vi);
103 if (ret < 0) {
104 perror("ioctl FBIOGET_VSCREENINFO");
105 return 1;
106 }
107
108 g_vi.xres = g_vi.xres_virtual = WIDTH;
109 g_vi.yres = HEIGHT;
110 g_vi.yres_virtual = HEIGHT * 2;
111 g_vi.bits_per_pixel = 16;
112
113 ret = ioctl(g_fd, FBIOPUT_VSCREENINFO, &g_vi);
114 if (ret < 0) {
115 perror("ioctl FBIOPUT_VSCREENINFO");
116 return 1;
117 }
118
119 pi.enabled = 1;
120 ret = ioctl(g_fd, OMAPFB_SETUP_PLANE, &pi);
121 if (ret < 0) {
122 perror("ioctl OMAPFB_SETUP_PLANE (e2)");
123 return 1;
124 }
125
126 memset(&key, 0, sizeof(key));
127 key.key_type = OMAPFB_COLOR_KEY_VID_SRC;
128 key.trans_key = 0x07e0;
129
130 ret = ioctl(g_fd, OMAPFB_SET_COLOR_KEY, &key);
131 if (ret < 0) {
132 perror("ioctl OMAPFB_SET_COLOR_KEY");
133 return 1;
134 }
135
136 return 0;
137}
138
139static int check_layer(void)
140{
141 struct omapfb_plane_info pi;
142 int ret;
143
144 ret = ioctl(g_fd, OMAPFB_QUERY_PLANE, &pi);
145 if (ret < 0) {
146 perror("ioctl OMAPFB_QUERY_PLANE");
147 return 1;
148 }
149
150 if (!pi.enabled) {
151 printf("something disabled the layer (tv-out?), quitting\n");
152 return 1;
153 }
154
155 return 0;
156}
157
158static void remove_layer(void)
159{
160 struct omapfb_plane_info pi;
161 struct omapfb_mem_info mi;
162 int ret;
163
164 memset(&pi, 0, sizeof(pi));
165 pi.enabled = 0;
166 ret = ioctl(g_fd, OMAPFB_SETUP_PLANE, &pi);
167 if (ret < 0)
168 perror("ioctl OMAPFB_SETUP_PLANE (c)");
169
170 ret = ioctl(g_fd, OMAPFB_QUERY_MEM, &mi);
171 if (ret < 0) {
172 perror("ioctl OMAPFB_QUERY_MEM");
173 goto out;
174 }
175
176 if (mi.size != g_old_mem) {
177 mi.size = g_old_mem;
178 ret = ioctl(g_fd, OMAPFB_SETUP_MEM, &mi);
179 if (ret < 0) {
180 perror("ioctl SETUP_MEM");
181 goto out;
182 }
183 }
184
185out:
186 close(g_fd);
187 g_fd = -1;
188}
189
190static void handle_signal(int num)
191{
192 g_exit = 1;
193}
194
195static void flip_fb(void)
196{
197 static int id;
198
199 g_vi.yoffset = id * HEIGHT;
200 ioctl(g_fd, FBIOPAN_DISPLAY, &g_vi);
201 id ^= 1;
202 g_screen = g_screen_base + id * WIDTH * HEIGHT;
203}
204
205#define s_printf(x, y, fmt, ...) \
206 basic_text_out16(g_screen, WIDTH, x, y, fmt, ##__VA_ARGS__);
207
208static int read_int_file(const char *fn, int *val)
209{
210 FILE *f;
211 int ret;
212
213 *val = 0;
214 f = fopen(fn, "r");
215 if (f == NULL)
216 return 1;
217 ret = fscanf(f, "%d", val);
218 fclose(f);
219 if (ret != 1)
220 return 1;
221
222 return 0;
223}
224
225static void clear(int h)
226{
227 int *p = (int *)g_screen;
228 int l = WIDTH * h / 2;
229
230 for (; l >= 4; l -= 4, p += 4)
231 p[0] = p[1] = p[2] = p[3] = 0x07e007e0;
232 for (; l > 0; l--, p++)
233 *p = 0x07e007e0;
234}
235
236struct proc_stat {
237 unsigned long idlesum;
238 unsigned long sys;
239 unsigned long irqs;
240 unsigned long ctxt;
241};
242
243static struct {
244 struct proc_stat last_st;
245 int cpu_usage;
246 int cpu_sys;
247 int irqs;
248 int ctxt;
249 int mem_free;
250 int mem_huge;
251 int mem_free_swap;
252} stats;
253
254static void collect_proc_stat(void)
255{
256 static long hz;
257 struct proc_stat st;
258 char buf[256];
259 unsigned long wait;
260 unsigned long iowait;
261 FILE *f;
262
263 if (hz == 0) {
264 hz = sysconf(_SC_CLK_TCK);
265 printf("hz: %ld\n", hz);
266 }
267
268 f = fopen("/proc/stat", "r");
269 if (f == NULL)
270 return;
271
272 if (fscanf(f, "cpu %*s %*s %lu %lu %lu",
273 &st.sys, &wait, &iowait) != 3)
274 goto out;
275 do {
276 if (fgets(buf, sizeof(buf), f) == NULL)
277 goto out;
278 } while (fscanf(f, "intr %lu", &st.irqs) != 1);
279 do {
280 if (fgets(buf, sizeof(buf), f) == NULL)
281 goto out;
282 } while (fscanf(f, "ctxt %lu", &st.ctxt) != 1);
283
284 st.idlesum = wait + iowait;
285
286 stats.cpu_usage = hz - (st.idlesum - stats.last_st.idlesum);
287 stats.cpu_sys = st.sys - stats.last_st.sys;
288 if (hz != 100) {
289 stats.cpu_usage = stats.cpu_usage * 100 / hz;
290 stats.cpu_sys = stats.cpu_sys * 100 / hz;
291 }
292 stats.irqs = st.irqs - stats.last_st.irqs;
293 stats.ctxt = st.ctxt - stats.last_st.ctxt;
294
295out:
296 stats.last_st = st;
297 fclose(f);
298}
299
300static void collect_mem_stat(void)
301{
302 static const char fn[] = "/proc/meminfo";
303 uint32_t total = 0, free = 0, buffers = 0, cached = 0;
304 uint32_t anonhuge = 0, hugetot = 0, hugefree = 0, hugepgsz = 0;
305 uint32_t free_swap = 0;
306 char buf[128];
307 FILE *f;
308
309 f = fopen(fn, "r");
310 if (f == NULL)
311 return;
312
313 while (fgets(buf, sizeof(buf), f)) {
314 if (total == 0 && sscanf(buf, "MemTotal: %u", &total) == 1)
315 continue;
316 if (free == 0 && sscanf(buf, "MemFree: %u", &free) == 1)
317 continue;
318 if (buffers == 0 && sscanf(buf, "Buffers: %u", &buffers) == 1)
319 continue;
320 if (cached == 0 && sscanf(buf, "Cached: %u", &cached) == 1)
321 continue;
322 if (free_swap == 0 && sscanf(buf, "SwapFree: %u", &free_swap) == 1)
323 continue;
324 if (anonhuge == 0 && sscanf(buf, "AnonHugePages: %u", &anonhuge) == 1)
325 continue;
326 if (hugetot == 0 && sscanf(buf, "HugePages_Total: %u", &hugetot) == 1)
327 continue;
328 if (hugefree == 0 && sscanf(buf, "HugePages_Free: %u", &hugefree) == 1)
329 continue;
330 if (hugepgsz == 0 && sscanf(buf, "Hugepagesize: %u", &hugepgsz) == 1)
331 continue;
332 }
333
334 fclose(f);
335
336 hugetot = (hugetot - hugefree) * hugepgsz;
337 free += buffers + cached;
338 stats.mem_free = free >> 10;
339 stats.mem_huge = hugetot >> 10;
340 stats.mem_free_swap = free_swap >> 10;
341}
342
343struct io_stat {
344 unsigned long sec_r;
345 unsigned long sec_w;
346};
347
348static struct {
349 struct io_stat last_st;
350 int read; // kB/s
351 int write;
352} io_stats;
353
354static void collect_io_stat(void)
355{
356 unsigned long sec_r, sec_w;
357 struct io_stat st = { 0, };
358 char buf[128];
359 FILE *f;
360 int i;
361
362 for (i = 0; i < 2; i++) {
363 snprintf(buf, sizeof(buf), "/sys/block/mmcblk%d/stat", i);
364 f = fopen(buf, "r");
365 if (f == NULL)
366 continue;
367
368 if (fscanf(f, "%*s %*s %lu %*s %*s %*s %lu",
369 &sec_r, &sec_w) == 2)
370 {
371 st.sec_r += sec_r;
372 st.sec_w += sec_w;
373 }
374 fclose(f);
375 }
376
377 io_stats.read = st.sec_r - io_stats.last_st.sec_r;
378 io_stats.write = st.sec_w - io_stats.last_st.sec_w;
379 // assuming 512 byte sectors..
380 io_stats.read >>= 1;
381 io_stats.write >>= 1;
382
383 io_stats.last_st = st;
384}
385
386struct net_stat {
387 unsigned long rx;
388 unsigned long tx;
389};
390
391static struct {
392 struct net_stat last_st;
393 int down; // kB/s
394 int up;
395 int have_wlan;
396 int link;
397 int level;
398} net_stats;
399
400static void collect_net_stat(void)
401{
402 struct net_stat st = { 0, };
403 unsigned long rx, tx;
404 int found = 0;
405 char buf[256];
406 FILE *f;
407
408 net_stats.down = net_stats.up =
409 net_stats.have_wlan = net_stats.link = net_stats.level = 0;
410
411 f = fopen("/proc/net/dev", "r");
412 if (f == NULL)
413 return;
414
415 while (!feof(f)) {
416 static const char fmt[] =
417 "%255s %lu %*s %*s %*s %*s %*s %*s %lu";
418 if (fscanf(f, fmt, buf, &rx, &tx) != 3)
419 continue;
420 if (IS_START(buf, "lo"))
421 continue;
422 st.rx += rx;
423 st.tx += tx;
424 found = 1;
425 }
426 fclose(f);
427
428 if (found) {
429 net_stats.down = st.rx - net_stats.last_st.rx;
430 net_stats.up = st.tx - net_stats.last_st.tx;
431 }
432 net_stats.last_st = st;
433
434 f = fopen("/proc/net/wireless", "r");
435 if (f == NULL)
436 return;
437
438 while (!feof(f)) {
439 int lnk, lvl;
440 if (fscanf(f, "%255s %*x %d. %d.", buf, &lnk, &lvl) != 3)
441 continue;
442 if (IS_START(buf, "lo"))
443 continue;
444 // first one should be enough
445 net_stats.have_wlan = 1;
446 net_stats.link = lnk;
447 net_stats.level = lvl;
448 break;
449 }
450 fclose(f);
451}
452
453static void collect_stats(void)
454{
455 collect_proc_stat();
456 collect_mem_stat();
457 collect_io_stat();
458 collect_net_stat();
459}
460
461static char cpu_reg_path[64];
462
463static void init_stats(void)
464{
465 char buf[64];
466 int found;
467 FILE *f;
468 int i;
469
470 // find the CPU regulator
471 for (i = found = 0; ; i++) {
472 snprintf(buf, sizeof(buf),
473 "/sys/class/regulator/regulator.%d/name", i);
474 f = fopen(buf, "r");
475 if (f == NULL)
476 break;
477 fgets(buf, sizeof(buf), f);
478 fclose(f);
479 if (IS_START(buf, "vdd_mpu")) {
480 found = 1;
481 break;
482 }
483 }
484
485 if (found)
486 snprintf(cpu_reg_path, sizeof(cpu_reg_path),
487 "/sys/class/regulator/regulator.%d/microvolts", i);
488
489 // do collect so differential stats get sane values
490 collect_stats();
491}
492
493static void get_time(int x, int y)
494{
495 struct timeval tv;
496 struct tm *t;
497 char buf[16];
498
499 gettimeofday(&tv, NULL);
500 t = localtime(&tv.tv_sec);
501 strftime(buf, sizeof(buf), "%T", t);
502 s_printf(x, y, " %s", buf);
503}
504
505static void get_cpu(int x, int y)
506{
507 s_printf(x, y, "CPU:%5d%%", stats.cpu_usage);
508}
509
510static void get_sys(int x, int y)
511{
512 s_printf(x, y, "SYS:%5d%%", stats.cpu_sys);
513}
514
515static void get_irq(int x, int y)
516{
517 s_printf(x, y, "IRQ:%5u", stats.irqs);
518}
519
520static void get_cs(int x, int y)
521{
522 s_printf(x, y, "CS:%6u", stats.ctxt);
523}
524
525static void get_cpuf(int x, int y)
526{
527 static const char fn[] =
528 "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq";
529 int f;
530
531 if (read_int_file(fn, &f))
532 return;
533 s_printf(x, y, "f:%7dM", f / 1000);
534}
535
536static void get_cpuv(int x, int y)
537{
538 int v = 0;
539
540 if (!cpu_reg_path[0] || read_int_file(cpu_reg_path, &v))
541 return;
542 s_printf(x, y, "V:%7.3fV", (float)v / 1000000.0f);
543}
544
545static void get_memfree(int x, int y)
546{
547 s_printf(x, y, "M:%7dM", stats.mem_free);
548}
549
550static void get_memhuge(int x, int y)
551{
552 s_printf(x, y, "H:%7dM", stats.mem_huge);
553}
554
555static void get_memswap(int x, int y)
556{
557 s_printf(x, y, "S:%7dM", stats.mem_free_swap);
558}
559
560#define PS_BASE "/sys/class/power_supply/bq27500-0/"
561
562static void get_bcap(int x, int y)
563{
564 static const char fnc[] = PS_BASE "capacity";
565 int c;
566
567 if (read_int_file(fnc, &c))
568 return;
569 s_printf(x, y, "C:%7d%%", c);
570}
571
572static void get_btime(int x, int y)
573{
574 static const char fne[] = PS_BASE "time_to_empty_now";
575 static const char fnf[] = PS_BASE "time_to_full_now";
576 int t = 0;
577
578 if (read_int_file(fne, &t) || t == 0) {
579 if (read_int_file(fnf, &t))
580 return;
581 }
582 s_printf(x, y, "t:%4d:%02d", t / 3600, t / 60 % 60);
583}
584
585static void get_bvolts(int x, int y)
586{
587 static const char fnv[] = PS_BASE "voltage_now";
588 int volt = 0;
589
590 if (read_int_file(fnv, &volt))
591 return;
592
593 s_printf(x, y, "V:%7.3fV", (float)volt / 1000000.0f);
594}
595
596static void get_bwatts(int x, int y)
597{
598 static const char fnc[] = PS_BASE "current_now";
599 static const char fnv[] = PS_BASE "voltage_now";
600 int w, cur = 0, volt = 0;
601
602 if (read_int_file(fnc, &cur))
603 return;
604 if (read_int_file(fnv, &volt))
605 return;
606
607 w = (cur / 1000) * (volt / 1000);
608 s_printf(x, y, "P:%7.3fW", (float)w / 1000000.0f);
609}
610
611static void get_btemp(int x, int y)
612{
613 static const char fnt[] = PS_BASE "temp";
614 int t;
615
616 if (read_int_file(fnt, &t))
617 return;
618 s_printf(x, y, "T:%7.1fC", (float)t / 10.0f);
619}
620
621static void get_ioread(int x, int y)
622{
623 s_printf(x, y, "R:%5.1fM/s", (float)io_stats.read / 1000.0f);
624}
625
626static void get_iowrite(int x, int y)
627{
628 s_printf(x, y, "W:%5.1fM/s", (float)io_stats.write / 1000.0f);
629}
630
631static void get_netdown(int x, int y)
632{
633 s_printf(x, y, "D:%5uK/s", net_stats.down / 1000);
634}
635
636static void get_netup(int x, int y)
637{
638 s_printf(x, y, "U:%5uK/s", net_stats.up / 1000);
639}
640
641static void get_netsgnl(int x, int y)
642{
643 s_printf(x, y, "S:%5ddBm", net_stats.level);
644}
645
646int main(int argc, char *argv[])
647{
648 static const char socket_name[] = "\0livestats";
649 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
650 pthread_cond_t cond;
651 pthread_condattr_t condattr;
652 struct sockaddr_un sun;
653 struct timespec ts;
654 struct pollfd pfd;
655 int y = 0, y_max = 0;
656 int sock;
657 int ret;
658
659 // look for other instance
660 sock = socket(PF_UNIX, SOCK_STREAM, 0);
661 if (sock == -1) {
662 perror("socket PF_UNIX");
663 return 1;
664 }
665
666 memset(&sun, 0, sizeof(sun));
667 sun.sun_family = AF_UNIX;
668 memcpy(sun.sun_path, socket_name, sizeof(socket_name));
669
670 ret = connect(sock, (struct sockaddr *)&sun, sizeof(sun));
671 if (ret == 0) {
672 printf("other instance detected, sending quit command\n");
673 ret = send(sock, "quit", 4, 0);
674 if (ret != 4)
675 perror("send");
676 close(sock);
677 return 0;
678 }
679
680 // bind/listen for quit command
681 ret = bind(sock, (struct sockaddr *)&sun, sizeof(sun));
682 if (ret != 0) {
683 perror("bind");
684 close(sock);
685 return 1;
686 }
687
688 ret = listen(sock, 1);
689 if (ret != 0) {
690 perror("listen");
691 close(sock);
692 return 1;
693 }
694
695 pfd.fd = sock;
696 pfd.events = POLLIN | POLLPRI;
697 pfd.revents = 0;
698
699 // clean up on kill too
700 signal(SIGTERM, handle_signal);
701
702 ret = pthread_condattr_init(&condattr);
703 ret |= pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC);
704 ret |= pthread_cond_init(&cond, &condattr);
705 pthread_condattr_destroy(&condattr);
706 if (ret != 0) {
707 fprintf(stderr, "cond init failed\n");
708 return 1;
709 }
710
711 ret = setup_layer();
712 if (ret)
713 return ret;
714
715 g_screen_base = mmap(NULL, MEM_SIZE, PROT_READ | PROT_WRITE,
716 MAP_SHARED, g_fd, 0);
717 if (g_screen_base == MAP_FAILED) {
718 perror("mmap");
719 return 1;
720 }
721 g_screen = g_screen_base;
722 clear(HEIGHT);
723 flip_fb();
724 clear(HEIGHT);
725
726 pthread_mutex_lock(&mutex);
727 ret = clock_gettime(CLOCK_MONOTONIC, &ts);
728 if (ret != 0) {
729 perror("clock_gettime CLOCK_MONOTONIC");
730 return 1;
731 }
732
733 nice(-1);
734 init_stats();
735
736 while (!g_exit) {
737 collect_stats();
738
739 y += Y_STEP;
740 if (y > y_max)
741 y_max = y;
742 clear(y_max);
743
744 y = 0;
745 get_time (0, y += Y_STEP);
746 y += Y_STEP / 2;
747 get_cpu (0, y += Y_STEP);
748 get_sys (0, y += Y_STEP);
749 get_irq (0, y += Y_STEP);
750 get_cs (0, y += Y_STEP);
751 get_cpuf (0, y += Y_STEP);
752 get_cpuv (0, y += Y_STEP);
753 y += Y_STEP / 2;
754 s_printf(0, y += Y_STEP, "mem");
755 get_memfree(0, y += Y_STEP);
756 get_memswap(0, y += Y_STEP);
757 if (stats.mem_huge)
758 get_memhuge(0, y += Y_STEP);
759 y += Y_STEP / 2;
760 s_printf(0, y += Y_STEP, "i/o");
761 get_ioread (0, y += Y_STEP);
762 get_iowrite(0, y += Y_STEP);
763 y += Y_STEP / 2;
764 s_printf(0, y += Y_STEP, "net");
765 get_netdown(0, y += Y_STEP);
766 get_netup (0, y += Y_STEP);
767 if (net_stats.have_wlan)
768 get_netsgnl(0, y += Y_STEP);
769 y += Y_STEP / 2;
770 s_printf(0, y += Y_STEP, "batt");
771 get_bcap (0, y += Y_STEP);
772 get_btime (0, y += Y_STEP);
773 get_bvolts (0, y += Y_STEP);
774 get_bwatts (0, y += Y_STEP);
775 get_btemp (0, y += Y_STEP);
776
777 flip_fb();
778
779 if (check_layer() != 0)
780 break;
781 ret = poll(&pfd, 1, 0);
782 if (ret != 0) {
783 printf("poll returned %d\n", ret);
784 break;
785 }
786
787 ts.tv_sec++;
788 pthread_cond_timedwait(&cond, &mutex, &ts);
789 }
790
791 munmap(g_screen_base, MEM_SIZE);
792 remove_layer();
793 close(sock);
794
795 return 0;
796}
797
798// vim:expandtab:sw=2:ts=2