0.1 release
[pandora_liveinfo.git] / main.c
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
37 static struct fb_var_screeninfo g_vi;
38 static uint16_t *g_screen_base, *g_screen;
39 static unsigned int g_old_mem;
40 static int g_fd, g_exit;
41
42 #define IS_START(buf, str) \
43   !strncmp(buf, str, sizeof(str) - 1)
44
45 static 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
139 static 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
158 static 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
185 out:
186   close(g_fd);
187   g_fd = -1;
188 }
189
190 static void handle_signal(int num)
191 {
192   g_exit = 1;
193 }
194
195 static 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
208 static 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
225 static 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
236 struct proc_stat {
237   unsigned long idlesum;
238   unsigned long sys;
239   unsigned long irqs;
240   unsigned long ctxt;
241 };
242
243 static 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
254 static 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
295 out:
296   stats.last_st = st;
297   fclose(f);
298 }
299
300 static 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
343 struct io_stat {
344   unsigned long sec_r;
345   unsigned long sec_w;
346 };
347
348 static struct {
349   struct io_stat last_st;
350   int read; // kB/s
351   int write;
352 } io_stats;
353
354 static 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
386 struct net_stat {
387   unsigned long rx;
388   unsigned long tx;
389 };
390
391 static 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
400 static 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
453 static void collect_stats(void)
454 {
455   collect_proc_stat();
456   collect_mem_stat();
457   collect_io_stat();
458   collect_net_stat();
459 }
460
461 static char cpu_reg_path[64];
462
463 static 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
493 static 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
505 static void get_cpu(int x, int y)
506 {
507   s_printf(x, y, "CPU:%5d%%", stats.cpu_usage);
508 }
509
510 static void get_sys(int x, int y)
511 {
512   s_printf(x, y, "SYS:%5d%%", stats.cpu_sys);
513 }
514
515 static void get_irq(int x, int y)
516 {
517   s_printf(x, y, "IRQ:%5u", stats.irqs);
518 }
519
520 static void get_cs(int x, int y)
521 {
522   s_printf(x, y, "CS:%6u", stats.ctxt);
523 }
524
525 static 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
536 static 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
545 static void get_memfree(int x, int y)
546 {
547   s_printf(x, y, "M:%7dM", stats.mem_free);
548 }
549
550 static void get_memhuge(int x, int y)
551 {
552   s_printf(x, y, "H:%7dM", stats.mem_huge);
553 }
554
555 static 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
562 static 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
572 static 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
585 static 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
596 static 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
611 static 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
621 static 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
626 static 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
631 static void get_netdown(int x, int y)
632 {
633   s_printf(x, y, "D:%5uK/s", net_stats.down / 1000);
634 }
635
636 static void get_netup(int x, int y)
637 {
638   s_printf(x, y, "U:%5uK/s", net_stats.up / 1000);
639 }
640
641 static void get_netsgnl(int x, int y)
642 {
643   s_printf(x, y, "S:%5ddBm", net_stats.level);
644 }
645
646 int 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