| #define _GNU_SOURCE /* for sighandler_t */ |
| #define _POSIX_C_SOURCE 199309L /* for clock_gettime */ |
| #define _BSD_SOURCE /* for strsep */ |
| #include <features.h> |
| |
| #include <string.h> |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <time.h> |
| #include <sys/select.h> |
| #include <sys/time.h> |
| #include <sys/uio.h> |
| #include <sys/wait.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include <stacktrace.h> |
| |
| #include "fileops.h" |
| #include "gfiber-lt.h" |
| #include "pin.h" |
| |
| #define UNUSED __attribute((unused)) |
| |
| #define WRITE(s) write(2, s, strlen(s)) |
| #ifndef ARRAY_SIZE |
| #define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0])) |
| #endif |
| |
| /* |
| * We're polling at a very high frequency, which is a pain. This would be |
| * slightly less gross inside the kernel (for less context switching and |
| * because it could more easily use the tick interrupt instead of polling). |
| * |
| * This setting isn't as bad as it sounds, though, because we don't poll |
| * 100% of the time; we only do it for a fraction of a second every now |
| * and then. |
| */ |
| #define POLL_HZ 2000 // polls per sec |
| #define USEC_PER_TICK (1000000 / POLL_HZ) |
| |
| /* |
| * At this temp, if sysmgr isn't setting fan, jump to 100% as a failsafe. |
| * sysmgr has a per-platform setting, but we don't share code with that here. |
| * Setting to MAX(temp_max) from fancontrol.cc in sysmgr, which is 100 now. |
| */ |
| #define HIGH_TEMP_OVERRIDE 100.0 |
| |
| #define CHECK(x) do { \ |
| int rv = (x); \ |
| if (rv) { \ |
| fprintf(stderr, "CHECK: %s returned %d\n", #x, rv); \ |
| _exit(99); \ |
| } \ |
| } while (0) |
| |
| static int is_limited_leds; |
| static int platform_b0; |
| static PinHandle handle; |
| |
| // Turn the leds on or off depending on the bits in fields. Currently |
| // the bits are: |
| // 1: red |
| // 2: blue (green on B0) |
| // 4: activity (blue) |
| // 8: standby (bright white) |
| static void set_leds_from_bitfields(int fields, int brightness) { |
| // allow a way to disable led control |
| if (access("disable", R_OK) == 0) { |
| return; |
| } |
| if (is_limited_leds) { |
| // GFMS100 only has red and activity lights. Substitute activity for blue |
| // (they're both blue anyhow) and red+activity (purple) for standby. |
| if (fields & 0x02) fields |= 0x04; |
| if (fields & 0x08) fields |= 0x05; |
| } else if (platform_b0) { |
| // B0 fat devices are as above (limited_leds). |
| // B0 fat devices had the leds switched around, and the polarities |
| // inverted. |
| fields = ( (fields & 0x8) | |
| ((fields & 0x4) >> 1) | |
| ((fields & 0x2) >> 1) | |
| ((fields & 0x1) << 2)); |
| fields ^= 0x0f; |
| } |
| if (PinIsPresent(handle, PIN_LED_RED)) |
| PinSetValue(handle, PIN_LED_RED, (fields & 0x01) ? brightness : 0); |
| if (PinIsPresent(handle, PIN_LED_BLUE)) |
| PinSetValue(handle, PIN_LED_BLUE, (fields & 0x02) ? brightness : 0); |
| if (PinIsPresent(handle, PIN_LED_ACTIVITY)) |
| PinSetValue(handle, PIN_LED_ACTIVITY, (fields & 0x04) ? brightness : 0); |
| if (PinIsPresent(handle, PIN_LED_STANDBY)) |
| PinSetValue(handle, PIN_LED_STANDBY, (fields & 0x08) ? brightness : 0); |
| } |
| |
| |
| // read a file containing a single short string. |
| // Returns a static buffer. Be careful! |
| static char *read_file(const char *filename) { |
| static char buf[1024]; |
| int fd = open(filename, O_RDONLY); |
| if (fd >= 0) { |
| size_t got = read(fd, buf, sizeof(buf) - 1); |
| buf[got] = '\0'; |
| close(fd); |
| return buf; |
| } |
| buf[0] = '\0'; |
| return buf; |
| } |
| |
| |
| // create the given (empty) file. |
| static void create_file(const char *filename) { |
| // use O_EXCL here to save a close() syscall when it already exists |
| int fd = open(filename, O_WRONLY|O_CREAT|O_EXCL, 0666); |
| if (fd >= 0) close(fd); |
| } |
| |
| // read led_sequence from the given file. For example, if a file contains |
| // x5 0 1 0 2 0 0x0f |
| // that means 5/6 of a second off, then red, then off, then blue, then off, |
| // then all the lights on at once, for a total of 5 seconds. |
| // Enhanced in prism, if the pin is followed by "@<number>[x<number>]", then |
| // the number after the @ indicates brightness, and the number after x means |
| // the repetition. For example, 1@50x3 means turn red LED with brightness 50 |
| // for 3 times of period (compared with w/o this param, wich is equivalent as |
| // x1). |
| static char led_sequence[16]; |
| static int led_sequence_for_brightness[16]; |
| static unsigned led_sequence_len = 1; |
| static unsigned led_total_time = 1000; |
| static void read_led_sequence_file(const char *filename) { |
| char *buf = read_file(filename), *p; |
| led_sequence_len = 0; |
| led_total_time = 1000; |
| while ((p = strsep(&buf, " \t\n\r")) != NULL && |
| led_sequence_len <= sizeof(led_sequence)/sizeof(led_sequence[0])) { |
| if (!*p) continue; |
| if (p[0] == 'x') { |
| led_total_time = strtoul(p+1, NULL, 0) * 1000; |
| if (led_total_time > 10000) led_total_time = 10000; |
| if (led_total_time < 1000) led_total_time = 1000; |
| } else { |
| char *separator; |
| int brightness = 100, repetition = 1, rep_idx; |
| |
| int leds = strtoul(p, &separator, 0); |
| if (*separator == '@') { |
| brightness = strtoul(separator + 1, &separator, 0); |
| } |
| if (*separator == 'x') { |
| repetition = strtoul(separator + 1, NULL, 0); |
| } |
| for (rep_idx = 0; rep_idx < repetition; rep_idx++) { |
| if (led_sequence_len >= ARRAY_SIZE(led_sequence)) { |
| fprintf(stderr, "LED pattern is too large.\n"); |
| break; |
| } |
| led_sequence[led_sequence_len] = leds; |
| led_sequence_for_brightness[led_sequence_len] = brightness; |
| led_sequence_len++; |
| } |
| } |
| } |
| if (!led_sequence_len) { |
| led_sequence[0] = 1; // red = error |
| led_sequence_len = 1; |
| } |
| } |
| |
| // switch to the next led combination in led_sequence. |
| static void led_sequence_update(long long frac) { |
| int i = led_sequence_len * frac / led_total_time; |
| if (i >= (int)led_sequence_len) |
| i = led_sequence_len; |
| if (i < 0) |
| i = 0; |
| |
| // if the 'activity' file exists, unlink() will succeed, giving us exactly |
| // one inversion of the activity light. That causes exactly one delightful |
| // blink. |
| int activity_toggle = (unlink("activity") == 0) ? 0x04 : 0; |
| |
| set_leds_from_bitfields(led_sequence[i] ^ activity_toggle, |
| led_sequence_for_brightness[i]); |
| } |
| |
| |
| // Same as time(), but in monotonic clock milliseconds instead. |
| static long long msec_now(void) { |
| struct timespec ts; |
| CHECK(clock_gettime(CLOCK_MONOTONIC, &ts)); |
| return ((long long)ts.tv_sec) * 1000 + (ts.tv_nsec / 1000000); |
| } |
| |
| |
| // Same as time(), but in realtime milliseconds instead. |
| // Avoid using this when possible, as ntpd can make it jump around. |
| static long long msec_realtime_now(void) { |
| struct timeval tv; |
| gettimeofday(&tv, NULL); |
| return ((long long)tv.tv_sec) * 1000 + (tv.tv_usec / 1000); |
| } |
| |
| |
| // The offset of msec_now() vs. wall clock time. |
| // Don't use this for anything important, since you can't trust wall clock |
| // time on our devices. But it's useful for syncing LED blinking between |
| // devices. Because it's prettier. |
| static long long msec_offset(void) { |
| long long mono = msec_now(), real = msec_realtime_now(); |
| // The math here is slightly silly because C doesn't guarantee what happens |
| // with % of a negative number, and we want the offset to always come out |
| // positive, so that nothing weird will happen when mono < led_total_time |
| // (which is true right after boot). |
| return (((mono % led_total_time) - (real % led_total_time)) |
| + led_total_time) % led_total_time; |
| } |
| |
| |
| // like signal(), but always creates a one-shot signal handler |
| static void _signal(int sig, sighandler_t handler) { |
| struct sigaction act; |
| memset(&act, 0, sizeof(act)); |
| act.sa_handler = handler; |
| act.sa_flags = SA_NODEFER | SA_RESETHAND; |
| sigaction(sig, &act, NULL); |
| } |
| |
| |
| static volatile sig_atomic_t shutdown_sig = 0; |
| static void sig_handler(int sig) { |
| shutdown_sig = sig; |
| |
| // even in case of a segfault, we still want to try to shut down |
| // politely so we can fix the fan speed etc. writev() is a syscall |
| // so this sequence should be safe since it has no outside dependencies. |
| // fprintf() is not 100% safe in a signal handler. |
| char buf[] = { |
| '0' + (sig / 100 % 10), |
| '0' + (sig / 10 % 10), |
| '0' + (sig % 10), |
| }; |
| struct iovec iov[] = { |
| { "exiting on signal ", 18 }, |
| { buf, sizeof(buf)/sizeof(buf[0]) }, |
| { "\n", 1 }, |
| }; |
| writev(2, iov, sizeof(iov)/sizeof(iov[0])); |
| |
| if (sig != SIGINT && sig != SIGTERM) stacktrace(); |
| } |
| |
| |
| static void alarm_handler(UNUSED int sig) { |
| WRITE("\nexiting on SIGALRM\n"); |
| abort(); |
| } |
| |
| void run_gpio_mailbox(void) { |
| _signal(SIGALRM, alarm_handler); |
| alarm(30); // die loudly if we freeze for any reason (probably libnexus) |
| platform_b0 = (NULL != strstr(read_file("/proc/cpuinfo"), "BCM7425B0")); |
| int has_fan = PinIsPresent(handle, PIN_FAN_CHASSIS); |
| int has_reset_button = PinIsPresent(handle, PIN_BUTTON_RESET); |
| int has_cpu_temp = PinIsPresent(handle, PIN_TEMP_CPU); |
| int has_cpu_voltage = PinIsPresent(handle, PIN_MVOLTS_CPU); |
| |
| is_limited_leds = !PinIsPresent(handle, PIN_LED_BLUE) || !PinIsPresent(handle, PIN_LED_STANDBY); |
| |
| #if 0 |
| // check if cpu has been locked |
| if (PinIsSecureBoot(handle)) { |
| create_file("ledcontrol/secure_boot"); |
| } |
| #endif |
| |
| fprintf(stderr, "gpio mailbox running.\n"); |
| write_file_longlong_atomic("/var/run/gpio-mailbox", NULL, getpid()); |
| _signal(SIGINT, sig_handler); |
| _signal(SIGTERM, sig_handler); |
| _signal(SIGSEGV, sig_handler); |
| _signal(SIGBUS, sig_handler); |
| _signal(SIGFPE, sig_handler); |
| |
| int inner_loop_ticks = 0, msec_per_led = 0; |
| int fan_loop_count = 0; |
| long long last_time = 0, last_print_time = msec_now(), |
| last_led = 0, reset_start = 0, offset = msec_offset(); |
| long long fanspeed = -42, reset_amt = -42, readyval = -42; |
| double cpu_temp = -42.0, cpu_volts = -42.0; |
| int wantspeed_warned = -42, wantspeed = 0; |
| int fan_detected_speed = 0; |
| while (!shutdown_sig) { |
| long long now = msec_now(); |
| alarm(30); // die loudly if we freeze for 30 seconds or more |
| |
| // blink the leds |
| if (now - last_led >= msec_per_led) { |
| read_led_sequence_file("leds"); |
| assert(led_sequence_len > 0); |
| inner_loop_ticks = POLL_HZ / led_sequence_len + 1; |
| while (inner_loop_ticks > POLL_HZ / 16) { |
| // make sure we poll at least every 1/8 of a second, or else the |
| // activity light won't blink impressively enough. |
| inner_loop_ticks /= 2; |
| } |
| msec_per_led = led_total_time / led_sequence_len + 1; |
| last_led = now; |
| offset = msec_offset(); |
| create_file("leds-ready"); |
| } |
| led_sequence_update((now + led_total_time - offset) % led_total_time); |
| |
| if (now - last_time > 2000) { |
| if (has_fan) { |
| // set the fan speed control |
| char *wantspeed_str = read_file("fanpercent"); |
| if (wantspeed_str[0]) { |
| wantspeed = strtol(wantspeed_str, NULL, 0); |
| if (wantspeed < 0 || wantspeed > 100) { |
| if (wantspeed_warned != wantspeed) { |
| fprintf(stderr, |
| "gpio/fanpercent (%d) is invalid: must be 0-100\n", |
| wantspeed); |
| wantspeed_warned = wantspeed; |
| } |
| wantspeed = 100; |
| } else if (wantspeed < 100 && cpu_temp >= HIGH_TEMP_OVERRIDE) { |
| if (wantspeed_warned != wantspeed) { |
| fprintf(stderr, |
| "DANGER: fanpercent (%d) is too low for CPU temp %.2f; " |
| "using 100%%.\n", wantspeed, cpu_temp); |
| wantspeed_warned = wantspeed; |
| } |
| wantspeed = 100; |
| } else { |
| wantspeed_warned = -42; |
| } |
| } else { |
| if (wantspeed_warned != 1) |
| fprintf(stderr, |
| "gpio/fanpercent is empty: using default value\n"); |
| wantspeed_warned = 1; |
| wantspeed = 100; |
| } |
| (void) PinSetValue(handle, PIN_FAN_CHASSIS, wantspeed); |
| |
| // capture the fan cycle counter |
| write_file_longlong_atomic("fanspeed", &fanspeed, fan_detected_speed); |
| } |
| |
| // capture the CPU temperature and voltage |
| int cpu_temp_millidegrees; |
| if (PinValue(handle, PIN_TEMP_CPU, &cpu_temp_millidegrees) == 0) { |
| write_file_double_atomic("cpu_temperature", &cpu_temp, cpu_temp_millidegrees / 1000.0); |
| } |
| int cpu_millivolts; |
| if (!has_cpu_voltage) { |
| write_file_double_atomic("cpu_voltage", &cpu_volts, 0.0); |
| } else if (PinValue(handle, PIN_MVOLTS_CPU, &cpu_millivolts) == 0) { |
| write_file_double_atomic("cpu_voltage", &cpu_volts, cpu_millivolts / 1000.0); |
| } |
| last_time = now; |
| } |
| |
| int reset_button = 0; |
| if (has_reset_button) { |
| (void) PinValue(handle, PIN_BUTTON_RESET, &reset_button); |
| } |
| |
| if (now - last_print_time >= 6000) { |
| if (has_fan) { |
| fprintf(stderr, "fan:%lld/sec:%d%% reads:%d ", fanspeed, wantspeed, 0); |
| } |
| if (has_reset_button) { |
| fprintf(stderr, "button:%d ", reset_button); |
| } |
| if (has_cpu_temp) { |
| fprintf(stderr, "temp:%.2f ", cpu_temp); |
| } |
| if (has_cpu_voltage) { |
| fprintf(stderr, "volts:%.2f", cpu_volts); |
| } |
| fprintf(stderr, "\n"); |
| last_print_time = now; |
| } |
| |
| // handle the reset button |
| if (reset_button) { |
| if (!reset_start) reset_start = now - 1; |
| write_file_longlong_atomic("reset_button_msecs", &reset_amt, now - reset_start); |
| } else { |
| if (reset_amt) unlink("reset_button_msecs"); |
| reset_amt = reset_start = 0; |
| } |
| |
| // this is last. it indicates we've made it once through the loop, |
| // so all the files in /tmp/gpio have been written at least once. |
| write_file_longlong_atomic("ready", &readyval, 1); |
| |
| if (has_fan) { |
| // poll for fan ticks. This is a bit complicated since we want to be |
| // sure to count the exact time for an integer number of ticks. |
| fan_loop_count = (fan_loop_count + 1) % 16; |
| if (!fan_loop_count) { |
| PinValue(handle, PIN_FAN_CHASSIS, &fan_detected_speed); |
| } else { |
| // no need to poll *every* time. |
| // For the last tick of each second, adjust it slightly so our LED |
| // blinks can be aligned on the led_total_time boundary. |
| long long time_to_boundary = |
| (led_total_time - (now - offset) % led_total_time) * 1000; |
| long long delay = USEC_PER_TICK * inner_loop_ticks; |
| if (delay > time_to_boundary) delay = time_to_boundary; |
| usleep(delay); |
| } |
| } else { |
| // platform has no fan |
| usleep(USEC_PER_TICK * inner_loop_ticks); |
| } |
| } |
| |
| // shut down cleanly |
| |
| #ifndef GFIBER_LT |
| set_leds_from_bitfields(1, 1); // red light to indicate a problem |
| #else |
| set_leds_from_bitfields(1, GFLT_DEFAULT_BRIGHTNESS); |
| #endif |
| |
| if (has_fan) (void) PinSetValue(handle, PIN_FAN_CHASSIS, 100); // for safety |
| } |
| |
| |
| static void parent_died(void) { |
| // normally the child process does this step. |
| // |
| // do it again here just in case the child process dies early; the boot |
| // process will wait on this file, and we don't want it to get jammed |
| // forever. |
| int fd = open("/var/run/gpio-mailbox", O_WRONLY|O_CREAT, 0666); |
| if (fd >= 0) close(fd); |
| } |
| |
| |
| static void parent_sighandler(int sig) { |
| WRITE("\n\nOWNER PROCESS DIED\n\n"); |
| parent_died(); |
| kill(getpid(), sig); |
| // should never get here, but just in case |
| abort(); |
| } |
| |
| |
| int main(void) { |
| int status = 98; |
| fprintf(stderr, "starting gpio mailbox in /tmp/gpio.\n"); |
| _signal(SIGSEGV, parent_sighandler); |
| _signal(SIGBUS, parent_sighandler); |
| _signal(SIGFPE, parent_sighandler); |
| |
| mkdir("/tmp/gpio", 0775); |
| if (chdir("/tmp/gpio") != 0) { |
| perror("chdir /tmp/gpio"); |
| return 1; |
| } |
| mkdir("/tmp/leds", 0775); |
| |
| handle = PinCreate(); |
| if (handle == NULL) { |
| fprintf(stderr, "PinCreate() failed\n"); |
| exit(status); |
| } |
| |
| run_gpio_mailbox(); |
| |
| parent_died(); |
| exit(status); |
| } |