| #include <assert.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/select.h> |
| #include <sys/time.h> |
| #include <sys/uio.h> |
| #include <sys/wait.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include "nexus_types.h" |
| #include "nexus_platform.h" |
| #include "nexus_gpio.h" |
| #include "nexus_gpio_init.h" |
| #include "nexus_pwm.h" |
| #include "nexus_temp_monitor.h" |
| #include "nexus_avs.h" |
| |
| /* |
| * 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) |
| |
| #define PWM_50_KHZ 0x7900 |
| #define PWM_26_KHZ 0x4000 |
| #define PWM_206_HZ 0x0080 |
| |
| #define CHECK(x) do { \ |
| int rv = (x); \ |
| if (rv) { \ |
| fprintf(stderr, "CHECK: %s returned %d\n", #x, rv); \ |
| _exit(99); \ |
| } \ |
| } while (0) |
| |
| static int platform_limited_leds, platform_b0; |
| |
| |
| struct Gpio { |
| NEXUS_GpioType type; |
| unsigned int pin; |
| NEXUS_GpioMode mode; |
| NEXUS_GpioInterrupt interrupt_mode; |
| |
| NEXUS_GpioHandle handle; |
| int old_val; |
| }; |
| |
| |
| struct Pwm { |
| unsigned int channel; |
| |
| NEXUS_PwmChannelHandle handle; |
| int old_percent; |
| }; |
| |
| |
| struct Gpio led_red = { |
| NEXUS_GpioType_eAonStandard, 17, |
| NEXUS_GpioMode_eOutputPushPull, NEXUS_GpioInterrupt_eDisabled, 0, -1 |
| }; |
| struct Gpio led_blue = { |
| NEXUS_GpioType_eAonStandard, 12, |
| NEXUS_GpioMode_eOutputPushPull, NEXUS_GpioInterrupt_eDisabled, 0, -1 |
| }; |
| struct Gpio led_activity = { |
| NEXUS_GpioType_eAonStandard, 13, |
| NEXUS_GpioMode_eOutputPushPull, NEXUS_GpioInterrupt_eDisabled, 0, -1 |
| }; |
| struct Gpio led_standby = { |
| NEXUS_GpioType_eAonStandard, 10, |
| NEXUS_GpioMode_eOutputPushPull, NEXUS_GpioInterrupt_eDisabled, 0, -1 |
| }; |
| |
| struct Gpio reset_button = { |
| NEXUS_GpioType_eAonStandard, 4, |
| NEXUS_GpioMode_eInput, NEXUS_GpioInterrupt_eDisabled/*eEdge*/, 0, -1 |
| }; |
| |
| struct Gpio fan_tick = { |
| NEXUS_GpioType_eStandard, 98, |
| NEXUS_GpioMode_eInput, NEXUS_GpioInterrupt_eDisabled/*eFallingEdge*/, 0, -1 |
| }; |
| |
| struct Pwm fan_control = { 0, 0, -1 }; |
| |
| |
| // Open the given PWM. You have to do this before writing it. |
| static void pwm_open(struct Pwm *p) { |
| NEXUS_PwmChannelSettings settings; |
| NEXUS_Pwm_GetDefaultChannelSettings(&settings); |
| settings.eFreqMode = NEXUS_PwmFreqModeType_eConstant; |
| p->handle = NEXUS_Pwm_OpenChannel(p->channel, &settings); |
| if (!p->handle) { |
| fprintf(stderr, "Pwm_Open returned null\n"); |
| _exit(1); |
| } |
| } |
| |
| |
| // Set the given PWM (pulse width modulator) to the given percent duty cycle. |
| static void set_pwm(struct Pwm *p, int percent) { |
| if (percent < 0) percent = 0; |
| if (percent > 100) percent = 100; |
| if (p->old_percent == percent) return; |
| p->old_percent = percent; |
| CHECK(NEXUS_Pwm_SetControlWord(p->handle, PWM_26_KHZ)); |
| CHECK(NEXUS_Pwm_SetPeriodInterval(p->handle, 99)); |
| CHECK(NEXUS_Pwm_SetOnInterval(p->handle, percent)); |
| CHECK(NEXUS_Pwm_Start(p->handle)); |
| } |
| |
| |
| // Get the CPU temperature. I think it's in Celsius. |
| static double get_cpu_temperature(void) { |
| NEXUS_AvsStatus status; |
| CHECK(NEXUS_GetAvsStatus(&status)); |
| return status.temperature / 100 / 10.0; // round to nearest 0.1 |
| } |
| |
| |
| // Get the CPU voltage. |
| static double get_cpu_voltage(void) { |
| NEXUS_AvsStatus status; |
| CHECK(NEXUS_GetAvsStatus(&status)); |
| return status.voltage / 10 / 100.0; // round to nearest 0.01 |
| } |
| |
| |
| // Open the given GPIO pin. You have to do this before reading or writing it. |
| static void gpio_open(struct Gpio *g) { |
| NEXUS_GpioSettings settings; |
| |
| NEXUS_Gpio_GetDefaultSettings(g->type, &settings); |
| settings.mode = g->mode; |
| settings.interruptMode = g->interrupt_mode; |
| settings.value = NEXUS_GpioValue_eLow; |
| g->handle = NEXUS_Gpio_Open(g->type, g->pin, &settings); |
| if (!g->handle) { |
| fprintf(stderr, "Gpio_Open returned null\n"); |
| _exit(1); |
| } |
| } |
| |
| |
| // Write the given GPIO pin. |
| // I don't actually know what's the difference between eHigh and eMax. |
| static void set_gpio(struct Gpio *g, int level) { |
| if (g->old_val == level) { |
| // If this is the same value as last time, don't do anything, for two |
| // reasons: |
| // 1) If you set the gpio too often, it seems to stay low (the led |
| // stays off). |
| // 2) If some process other than us is twiddling a led, this way we |
| // won't interfere with it. |
| return; |
| } |
| g->old_val = level; |
| |
| NEXUS_GpioValue val; |
| switch (level) { |
| case 0: val = NEXUS_GpioValue_eLow; break; |
| case 1: val = NEXUS_GpioValue_eHigh; break; |
| case 2: val = NEXUS_GpioValue_eMax; break; |
| default: |
| assert(level >= 0); |
| assert(level <= 2); |
| val = NEXUS_GpioValue_eLow; |
| break; |
| } |
| |
| NEXUS_GpioSettings settings; |
| NEXUS_Gpio_GetSettings(g->handle, &settings); |
| settings.value = val; |
| NEXUS_Gpio_SetSettings(g->handle, &settings); |
| } |
| |
| |
| // Read the given GPIO pin |
| static NEXUS_GpioValue get_gpio(struct Gpio *g) { |
| NEXUS_GpioStatus status; |
| CHECK(NEXUS_Gpio_GetStatus(g->handle, &status)); |
| return status.value; |
| } |
| |
| |
| // 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) { |
| if (platform_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; |
| } |
| set_gpio(&led_red, (fields & 0x01) ? 1 : 0); |
| set_gpio(&led_blue, (fields & 0x02) ? 1 : 0); |
| set_gpio(&led_activity, (fields & 0x04) ? 1 : 0); |
| set_gpio(&led_standby, (fields & 0x08) ? 1 : 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); |
| } |
| |
| |
| // write a file containing the given string. |
| static void write_file(const char *filename, const char *content) { |
| char *tmpname = malloc(strlen(filename) + 4 + 1); |
| sprintf(tmpname, "%s.tmp", filename); |
| int fd = open(tmpname, O_WRONLY|O_CREAT|O_TRUNC, 0666); |
| if (fd >= 0) { |
| write(fd, content, strlen(content)); |
| close(fd); |
| rename(tmpname, filename); |
| } |
| free(tmpname); |
| } |
| |
| |
| // write a file containing just a single integer value (as a string, not |
| // binary) |
| static void write_file_int(const char *filename, |
| long long *oldv, long long newv) { |
| if (!oldv || *oldv != newv) { |
| char buf[128]; |
| snprintf(buf, sizeof(buf), "%lld", newv); |
| buf[sizeof(buf)-1] = 0; |
| write_file(filename, buf); |
| if (oldv) *oldv = newv; |
| } |
| } |
| |
| |
| // write a file containing just a single floating point value (as a string, |
| // not binary) |
| static void write_file_float(const char *filename, |
| double *oldv, double newv) { |
| if (!oldv || *oldv != newv) { |
| char buf[128]; |
| snprintf(buf, sizeof(buf), "%.2f", newv); |
| buf[sizeof(buf)-1] = 0; |
| write_file(filename, buf); |
| if (oldv) *oldv = newv; |
| } |
| } |
| |
| |
| // read led_sequence from the given file. For example, if a file contains |
| // 0 1 0 2 0 0x0f |
| // that means 1/6 of a second off, then red, then off, then blue, then off, |
| // then all the lights on at once. |
| static char led_sequence[16]; |
| static unsigned led_sequence_len = 1; |
| static void read_led_sequence_file(const char *filename) { |
| char *buf = read_file(filename), *p; |
| led_sequence_len = 0; |
| while ((p = strsep(&buf, " \t\n\r")) != NULL && |
| led_sequence_len <= sizeof(led_sequence)/sizeof(led_sequence[0])) { |
| if (!*p) continue; |
| led_sequence[led_sequence_len++] = strtol(p, NULL, 0); |
| } |
| 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 / 1000; |
| 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); |
| } |
| |
| |
| // Same as time(), but in 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); |
| } |
| |
| |
| // 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(); |
| struct timeval tv; |
| gettimeofday(&tv, NULL); |
| return ((mono - (tv.tv_usec / 1000)) + 1000) % 1000; |
| } |
| |
| |
| static volatile int shutdown_sig = 0; |
| static void sig_handler(int sig) { |
| shutdown_sig = sig; |
| signal(sig, SIG_DFL); |
| |
| // 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])); |
| } |
| |
| |
| void run_gpio_mailbox(void) { |
| platform_limited_leds = (0 == strncmp(read_file("/etc/platform"), |
| "GFMS100", 7)); |
| platform_b0 = (NULL != strstr(read_file("/proc/cpuinfo"), "BCM7425B0")); |
| gpio_open(&led_standby); |
| gpio_open(&led_red); |
| gpio_open(&led_activity); |
| gpio_open(&led_blue); |
| gpio_open(&reset_button); |
| gpio_open(&fan_tick); |
| pwm_open(&fan_control); |
| |
| // close any extra fds, especially /dev/brcm0. That way we're |
| // certain we won't interfere with any other nexus process's interrupt |
| // handling. Only one process can be doing interrupt handling at a time. |
| for (int i = 3; i < 100; i++) |
| close(i); |
| |
| fprintf(stderr, "gpio mailbox running.\n"); |
| write_file_int("/var/run/gpio-mailbox", NULL, getpid()); |
| signal(SIGINT, sig_handler); |
| signal(SIGTERM, sig_handler); |
| signal(SIGSEGV, sig_handler); |
| signal(SIGFPE, sig_handler); |
| |
| int inner_loop_ticks = 0, msec_per_led = 0; |
| int reads = 0, fan_flips = 0, fan_loop_count = 0; |
| long long last_time = 0, last_print_time = msec_now(), |
| last_led = 0, reset_start = 0, fan_loop_time = 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; |
| while (!shutdown_sig) { |
| long long now = msec_now(); |
| |
| // 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 = 1000 / led_sequence_len + 1; |
| last_led = now; |
| offset = msec_offset(); |
| create_file("leds-ready"); |
| } |
| led_sequence_update((now + 1000 - offset) % 1000); |
| |
| if (now - last_time > 2000) { |
| // 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 >= 100.0) { |
| 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; |
| } |
| set_pwm(&fan_control, wantspeed); |
| |
| // capture the fan cycle counter |
| write_file_int("fanspeed", &fanspeed, |
| fan_flips * 1000 / (fan_loop_time + 1)); |
| fan_flips = fan_loop_time = 0; |
| |
| // capture the CPU temperature and voltage |
| write_file_float("cpu_temperature", &cpu_temp, get_cpu_temperature()); |
| write_file_float("cpu_voltage", &cpu_volts, get_cpu_voltage()); |
| last_time = now; |
| } |
| |
| if (now - last_print_time >= 6000) { |
| fprintf(stderr, |
| "fan:%lld/sec:%d%% reads:%d button:%d temp:%.2f volts:%.2f\n", |
| fanspeed, wantspeed, reads, |
| get_gpio(&reset_button), cpu_temp, cpu_volts); |
| reads = 0; |
| last_print_time = now; |
| } |
| |
| // handle the reset button |
| int reset = !get_gpio(&reset_button); // 0x1 means *not* pressed |
| if (reset) { |
| if (!reset_start) reset_start = now - 1; |
| write_file_int("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_int("ready", &readyval, 1); |
| |
| // 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) { |
| long long start = 0, end = 0; |
| int start_fan = get_gpio(&fan_tick), last_fan = start_fan; |
| for (int tick = 0; tick < inner_loop_ticks; tick++) { |
| int cur_fan = get_gpio(&fan_tick); |
| if (last_fan != cur_fan && start_fan == cur_fan) { |
| if (!start) { |
| start = msec_now(); |
| } else { |
| fan_flips++; |
| end = msec_now(); |
| } |
| } |
| reads++; |
| last_fan = cur_fan; |
| if (shutdown_sig) break; |
| usleep(USEC_PER_TICK); |
| } |
| fan_loop_time += end - start; |
| } 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 one-second boundary. |
| long long time_to_boundary = 1000000 - ((now - offset) * 1000) % 1000000; |
| long long delay = USEC_PER_TICK * inner_loop_ticks; |
| if (delay > time_to_boundary) delay = time_to_boundary; |
| usleep(delay); |
| } |
| } |
| |
| // shut down cleanly |
| |
| set_leds_from_bitfields(1); // red light to indicate a problem |
| set_pwm(&fan_control, 100); // for safety |
| |
| // do *not* clean up nicely in the child; we use _exit() instead of |
| // returning or calling exit(). A polite shutdown is what the parent |
| // process should have done. No need to do it twice. |
| if (shutdown_sig > 0) { |
| kill(getpid(), shutdown_sig); |
| } |
| _exit(0); |
| } |
| |
| |
| int main(void) { |
| int status = 98; |
| int pipefds[2]; |
| fprintf(stderr, "starting gpio mailbox in /tmp/gpio.\n"); |
| |
| mkdir("/tmp/gpio", 0775); |
| if (chdir("/tmp/gpio") != 0) { |
| perror("chdir /tmp/gpio"); |
| return 1; |
| } |
| mkdir("/tmp/leds", 0775); |
| |
| NEXUS_PlatformSettings platform_settings; |
| NEXUS_Platform_GetDefaultSettings(&platform_settings); |
| platform_settings.openFrontend = false; |
| if (NEXUS_Platform_Init(&platform_settings) != 0) |
| goto end; |
| |
| if (pipe(pipefds) != 0) { |
| perror("pipe"); |
| _exit(99); |
| } |
| |
| // Fork into the background so we can shut down most of our copy of nexus. |
| // Otherwise it leaves things like the video threads running, which results |
| // in a mess. But it happens that the gpio/pwm stuff is just done through |
| // mmap, which will be inherited across a fork, unlike all the extra junk |
| // threads. |
| pid_t pid = fork(); |
| if (pid < 0) { |
| perror("fork"); |
| _exit(99); |
| } else if (pid == 0) { |
| // child process. First, wait for the parent to finish its setup. |
| char buf[1]; |
| close(pipefds[1]); |
| if (read(pipefds[0], buf, 1) != 0) { |
| // this should just always return 0 == EOF. |
| perror("pipe-read"); |
| _exit(99); |
| } |
| close(pipefds[0]); |
| run_gpio_mailbox(); |
| _exit(0); |
| } |
| |
| // parent process. Uninit nexus here, to kill the unnecessary threads. |
| NEXUS_Platform_Uninit(); |
| |
| // release the child process now that our copy of Nexus is shut down |
| close(pipefds[0]); |
| close(pipefds[1]); |
| |
| // now wait for the child process to exit so we can propagate its exit |
| // code to our own parent, who can make decisions about restarting. |
| while (waitpid(pid, &status, 0) != pid) { } |
| |
| end: |
| // 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. |
| write_file_int("/var/run/gpio-mailbox", NULL, getpid()); |
| |
| exit(status); |
| } |