|
| 1 | +/* |
| 2 | +GPIO HALT: monitors a single GPIO pin, initiates orderly shutdown. |
| 3 | +Similar functionality to the rpi_power_switch kernel module from the |
| 4 | +fbtft project, but easier to compile (no kernel headers needed). |
| 5 | +
|
| 6 | +Connect button between any GND pin (there are several on the GPIO header) |
| 7 | +and the GPIO pin of interest. Internal pullup is used; no resistors needed. |
| 8 | +By default GPIO21 is used; this and GND are the last pins on the Model B+ |
| 9 | +GPIO (40 pin) header, so it's very easy to plug in a button quick-connect. |
| 10 | +Different pin can be specified on the command line or by editing the code. |
| 11 | +Avoid pins 8 and 10; these are configured as a serial port by default on |
| 12 | +most systems (this can be disabled but takes some doing). |
| 13 | +
|
| 14 | +To run automatically at startup, move the executable to /usr/local/bin and |
| 15 | +edit /etc/rc.local, inserting this one line before the final 'exit 0': |
| 16 | +
|
| 17 | +/usr/local/bin/gpio-halt & |
| 18 | +
|
| 19 | +An alternate pin number can optionally be specified before the '&' |
| 20 | +
|
| 21 | +This is mostly just a pared-down 'retrogame' from the Cupcade project. |
| 22 | +
|
| 23 | +Written by Phil Burgess for Adafruit Industries, distributed under BSD |
| 24 | +License. Adafruit invests time and resources providing this open source |
| 25 | +code, please support Adafruit and open-source hardware by purchasing |
| 26 | +products from Adafruit! |
| 27 | +
|
| 28 | +
|
| 29 | +Copyright (c) 2014 Adafruit Industries. |
| 30 | +All rights reserved. |
| 31 | +
|
| 32 | +Redistribution and use in source and binary forms, with or without |
| 33 | +modification, are permitted provided that the following conditions are met: |
| 34 | +
|
| 35 | +- Redistributions of source code must retain the above copyright notice, |
| 36 | + this list of conditions and the following disclaimer. |
| 37 | +- Redistributions in binary form must reproduce the above copyright notice, |
| 38 | + this list of conditions and the following disclaimer in the documentation |
| 39 | + and/or other materials provided with the distribution. |
| 40 | +
|
| 41 | +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| 42 | +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 43 | +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 44 | +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| 45 | +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 46 | +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 47 | +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 48 | +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 49 | +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 50 | +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 51 | +POSSIBILITY OF SUCH DAMAGE. |
| 52 | +*/ |
| 53 | + |
| 54 | +#include <stdio.h> |
| 55 | +#include <stdlib.h> |
| 56 | +#include <string.h> |
| 57 | +#include <unistd.h> |
| 58 | +#include <fcntl.h> |
| 59 | +#include <poll.h> |
| 60 | +#include <signal.h> |
| 61 | +#include <sys/mman.h> |
| 62 | + |
| 63 | +// A few globals --------------------------------------------------------- |
| 64 | + |
| 65 | +char |
| 66 | + *progName, // Program name (for error reporting) |
| 67 | + sysfs_root[] = "/sys/class/gpio", // Location of Sysfs GPIO files |
| 68 | + running = 1; // Signal handler will set to 0 (exit) |
| 69 | +int |
| 70 | + pin = 21; // Shutdown pin # (override w/argv) |
| 71 | +volatile unsigned int |
| 72 | + *gpio; // GPIO register table |
| 73 | +const int |
| 74 | + debounceTime = 20; // 20 ms for button debouncing |
| 75 | + |
| 76 | + |
| 77 | +// Some utility functions ------------------------------------------------ |
| 78 | + |
| 79 | +// Set one GPIO pin attribute through the Sysfs interface. |
| 80 | +int pinConfig(char *attr, char *value) { |
| 81 | + char filename[50]; |
| 82 | + int fd, w, len = strlen(value); |
| 83 | + sprintf(filename, "%s/gpio%d/%s", sysfs_root, pin, attr); |
| 84 | + if((fd = open(filename, O_WRONLY)) < 0) return -1; |
| 85 | + w = write(fd, value, len); |
| 86 | + close(fd); |
| 87 | + return (w != len); // 0 = success |
| 88 | +} |
| 89 | + |
| 90 | +// Un-export Sysfs pins; don't leave filesystem cruft. Write errors are |
| 91 | +// ignored as pins may be in a partially-initialized state. |
| 92 | +void cleanup() { |
| 93 | + char buf[50]; |
| 94 | + int fd; |
| 95 | + sprintf(buf, "%s/unexport", sysfs_root); |
| 96 | + if((fd = open(buf, O_WRONLY)) >= 0) { |
| 97 | + sprintf(buf, "%d", pin); |
| 98 | + write(fd, buf, strlen(buf)); |
| 99 | + close(fd); |
| 100 | + } |
| 101 | +} |
| 102 | + |
| 103 | +// Quick-n-dirty error reporter; print message, clean up and exit. |
| 104 | +void err(char *msg) { |
| 105 | + printf("%s: %s. Try 'sudo %s'.\n", progName, msg, progName); |
| 106 | + cleanup(); |
| 107 | + exit(1); |
| 108 | +} |
| 109 | + |
| 110 | +// Interrupt handler -- set global flag to abort main loop. |
| 111 | +void signalHandler(int n) { |
| 112 | + running = 0; |
| 113 | +} |
| 114 | + |
| 115 | +// Detect Pi board type. Doesn't return super-granular details, |
| 116 | +// just the most basic distinction needed for GPIO compatibility: |
| 117 | +// 0: Pi 1 Model B revision 1 |
| 118 | +// 1: Pi 1 Model B revision 2, Model A, Model B+, Model A+ |
| 119 | +// 2: Pi 2 Model B |
| 120 | + |
| 121 | +static int boardType(void) { |
| 122 | + FILE *fp; |
| 123 | + char buf[1024], *ptr; |
| 124 | + int n, board = 1; // Assume Pi1 Rev2 by default |
| 125 | + |
| 126 | + // Relies on info in /proc/cmdline. If this becomes unreliable |
| 127 | + // in the future, alt code below uses /proc/cpuinfo if any better. |
| 128 | +#if 1 |
| 129 | + if((fp = fopen("/proc/cmdline", "r"))) { |
| 130 | + while(fgets(buf, sizeof(buf), fp)) { |
| 131 | + if((ptr = strstr(buf, "mem_size=")) && |
| 132 | + (sscanf(&ptr[9], "%x", &n) == 1) && |
| 133 | + ((n == 0x3F000000) || (n == 0x40000000))) { |
| 134 | + board = 2; // Appears to be a Pi 2 |
| 135 | + break; |
| 136 | + } else if((ptr = strstr(buf, "boardrev=")) && |
| 137 | + (sscanf(&ptr[9], "%x", &n) == 1) && |
| 138 | + ((n == 0x02) || (n == 0x03))) { |
| 139 | + board = 0; // Appears to be an early Pi |
| 140 | + break; |
| 141 | + } |
| 142 | + } |
| 143 | + fclose(fp); |
| 144 | + } |
| 145 | +#else |
| 146 | + char s[8]; |
| 147 | + if((fp = fopen("/proc/cpuinfo", "r"))) { |
| 148 | + while(fgets(buf, sizeof(buf), fp)) { |
| 149 | + if((ptr = strstr(buf, "Hardware")) && |
| 150 | + (sscanf(&ptr[8], " : %7s", s) == 1) && |
| 151 | + (!strcmp(s, "BCM2709"))) { |
| 152 | + board = 2; // Appears to be a Pi 2 |
| 153 | + break; |
| 154 | + } else if((ptr = strstr(buf, "Revision")) && |
| 155 | + (sscanf(&ptr[8], " : %x", &n) == 1) && |
| 156 | + ((n == 0x02) || (n == 0x03))) { |
| 157 | + board = 0; // Appears to be an early Pi |
| 158 | + break; |
| 159 | + } |
| 160 | + } |
| 161 | + fclose(fp); |
| 162 | + } |
| 163 | +#endif |
| 164 | + |
| 165 | + return board; |
| 166 | +} |
| 167 | + |
| 168 | + |
| 169 | +// Main stuff ------------------------------------------------------------ |
| 170 | + |
| 171 | +#define PI1_BCM2708_PERI_BASE 0x20000000 |
| 172 | +#define PI1_GPIO_BASE (PI1_BCM2708_PERI_BASE + 0x200000) |
| 173 | +#define PI2_BCM2708_PERI_BASE 0x3F000000 |
| 174 | +#define PI2_GPIO_BASE (PI2_BCM2708_PERI_BASE + 0x200000) |
| 175 | +#define BLOCK_SIZE (4*1024) |
| 176 | +#define GPPUD (0x94 / 4) |
| 177 | +#define GPPUDCLK0 (0x98 / 4) |
| 178 | + |
| 179 | +int main(int argc, char *argv[]) { |
| 180 | + |
| 181 | + char buf[50], // For sundry filenames |
| 182 | + c, // Pin input value ('0'/'1') |
| 183 | + board; // 0=Pi1Rev1, 1=Pi1Rev2, 2=Pi2 |
| 184 | + int fd, // For mmap, sysfs, uinput |
| 185 | + timeout = -1, // poll() timeout |
| 186 | + pressed; // Last-read pin state |
| 187 | + volatile unsigned char shortWait; // Delay counter |
| 188 | + struct pollfd p; // GPIO file descriptor |
| 189 | + |
| 190 | + progName = argv[0]; // For error reporting |
| 191 | + signal(SIGINT , signalHandler); // Trap basic signals (exit cleanly) |
| 192 | + signal(SIGKILL, signalHandler); |
| 193 | + |
| 194 | + if(argc > 1) pin = atoi(argv[1]); |
| 195 | + |
| 196 | + // If this is a "Revision 1" Pi board (no mounting holes), |
| 197 | + // remap certain pin numbers for compatibility. |
| 198 | + board = boardType(); |
| 199 | + if(board == 0) { |
| 200 | + if( pin == 2) pin = 0; |
| 201 | + else if(pin == 3) pin = 1; |
| 202 | + else if(pin == 27) pin = 21; |
| 203 | + } |
| 204 | + |
| 205 | + // ---------------------------------------------------------------- |
| 206 | + // Although Sysfs provides solid GPIO interrupt handling, there's |
| 207 | + // no interface to the internal pull-up resistors (this is by |
| 208 | + // design, being a hardware-dependent feature). It's necessary to |
| 209 | + // grapple with the GPIO configuration registers directly to enable |
| 210 | + // the pull-ups. Based on GPIO example code by Dom and Gert van |
| 211 | + // Loo on elinux.org |
| 212 | + |
| 213 | + if((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) |
| 214 | + err("Can't open /dev/mem"); |
| 215 | + gpio = mmap( // Memory-mapped I/O |
| 216 | + NULL, // Any adddress will do |
| 217 | + BLOCK_SIZE, // Mapped block length |
| 218 | + PROT_READ|PROT_WRITE, // Enable read+write |
| 219 | + MAP_SHARED, // Shared with other processes |
| 220 | + fd, // File to map |
| 221 | + (board == 2) ? |
| 222 | + PI2_GPIO_BASE : // -> GPIO registers |
| 223 | + PI1_GPIO_BASE); |
| 224 | + close(fd); // Not needed after mmap() |
| 225 | + if(gpio == MAP_FAILED) err("Can't mmap()"); |
| 226 | + gpio[GPPUD] = 2; // Enable pullup |
| 227 | + for(shortWait=150;--shortWait;); // Min 150 cycle wait |
| 228 | + gpio[GPPUDCLK0] = 1 << pin; // Set pullup mask |
| 229 | + for(shortWait=150;--shortWait;); // Wait again |
| 230 | + gpio[GPPUD] = 0; // Reset pullup registers |
| 231 | + gpio[GPPUDCLK0] = 0; |
| 232 | + (void)munmap((void *)gpio, BLOCK_SIZE); // Done with GPIO mmap() |
| 233 | + |
| 234 | + // ---------------------------------------------------------------- |
| 235 | + // All other GPIO config is handled through the sysfs interface. |
| 236 | + |
| 237 | + sprintf(buf, "%s/export", sysfs_root); |
| 238 | + if((fd = open(buf, O_WRONLY)) < 0) // Open Sysfs export file |
| 239 | + err("Can't open GPIO export file"); |
| 240 | + sprintf(buf, "%d", pin); |
| 241 | + write(fd, buf, strlen(buf)); // Export pin |
| 242 | + pinConfig("active_low", "0"); // Don't invert |
| 243 | + // Set pin to input, detect rise+fall events |
| 244 | + if(pinConfig("direction", "in") || |
| 245 | + pinConfig("edge" , "both")) |
| 246 | + err("Pin config failed"); |
| 247 | + // Get initial pin value |
| 248 | + sprintf(buf, "%s/gpio%d/value", sysfs_root, pin); |
| 249 | + if((p.fd = open(buf, O_RDONLY)) < 0) |
| 250 | + err("Can't access pin value"); |
| 251 | + pressed = 0; |
| 252 | + if((read(p.fd, &c, 1) == 1) && (c == '0')) pressed = 1; |
| 253 | + p.events = POLLPRI; // Set up poll() events |
| 254 | + p.revents = 0; |
| 255 | + close(fd); // Done exporting |
| 256 | + |
| 257 | + // ---------------------------------------------------------------- |
| 258 | + // Monitor GPIO file descriptor for button events. The poll() |
| 259 | + // function watches for GPIO IRQs in this case; it is NOT |
| 260 | + // continually polling the pins! Processor load is near zero. |
| 261 | + |
| 262 | + while(running) { // Signal handler can set this to 0 to exit |
| 263 | + // Wait for IRQ on pin (or timeout for button debounce) |
| 264 | + if(poll(&p, 1, timeout) > 0) { // If IRQ... |
| 265 | + if(p.revents) { // Event received? |
| 266 | + // Read current pin state, store in |
| 267 | + // 'pressed' state flag, but don't halt |
| 268 | + // yet -- must wait for debounce! |
| 269 | + lseek(p.fd, 0, SEEK_SET); |
| 270 | + read(p.fd, &c, 1); |
| 271 | + if(c == '0') pressed = 1; |
| 272 | + else if(c == '1') pressed = 0; |
| 273 | + p.revents = 0; // Clear flag |
| 274 | + } |
| 275 | + timeout = debounceTime; // Set timeout for debounce |
| 276 | + // Else timeout occurred |
| 277 | + } else if(timeout == debounceTime) { // Button debounce timeout |
| 278 | + if(pressed) { |
| 279 | + (void)system("shutdown -h now"); |
| 280 | + running = 0; |
| 281 | + } |
| 282 | + } |
| 283 | + } |
| 284 | + |
| 285 | + // ---------------------------------------------------------------- |
| 286 | + // Clean up |
| 287 | + |
| 288 | + cleanup(); // Un-export pins |
| 289 | + |
| 290 | + puts("Done."); |
| 291 | + |
| 292 | + return 0; |
| 293 | +} |
0 commit comments