├── Makefile ├── LICENSE ├── README.md └── gpio-halt.c /Makefile: -------------------------------------------------------------------------------- 1 | EXECS = gpio-halt 2 | CFLAGS = -Wall -Ofast -fomit-frame-pointer -funroll-loops -s \ 3 | -I/opt/vc/include \ 4 | -I/opt/vc/include/interface/vcos/pthreads \ 5 | -I/opt/vc/include/interface/vmcs_host \ 6 | -I/opt/vc/include/interface/vmcs_host/linux \ 7 | -L/opt/vc/lib 8 | LIBS = -lbcm_host 9 | CC = gcc $(CFLAGS) 10 | 11 | all: $(EXECS) 12 | 13 | gpio-halt: gpio-halt.c 14 | $(CC) $< $(LIBS) -o $@ 15 | strip $@ 16 | 17 | install: 18 | mv $(EXECS) /usr/local/sbin 19 | chown root:root /usr/local/sbin/$(EXECS) 20 | 21 | clean: 22 | rm -f $(EXECS) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Adafruit Industries 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Adafruit-GPIO-Halt 2 | ================== 3 | 4 | Press-to-halt program for headless Raspberry Pi. Similar functionality to the rpi_power_switch kernel module from the fbtft project, but easier to compile (no kernel headers needed). 5 | 6 | # Modifications 7 | - Added a second argument, the time (in milliseconds) that the button should be kept pressed for halt to start. 8 | First argument, which was present in the original, is the GPIO pin (21 by default). 9 | - `Make install` will copy the executable to /usr/local/sbin (instead of /usr/local/bin) and set root as the owner. This makes more sense as power handling commands are usually owned by root and located in sbin directories. 10 | 11 | ## Install as a service 12 | (Based on this blog post: https://www.recantha.co.uk/blog/?p=13999) 13 | 14 | Create and open service file: 15 | ``` 16 | sudo vi /etc/systemd/system/gpio-halt.service 17 | ``` 18 | Add this content to the file: 19 | ``` 20 | [Unit] 21 | Description=GPIO shutdown (pin 21 to ground) 22 | After=multi-user.target 23 | 24 | [Service] 25 | Type=idle 26 | ExecStart=/usr/local/sbin/gpio-halt 21 3000 27 | 28 | [Install] 29 | WantedBy=multi-user.target 30 | ``` 31 | Start the script: 32 | ``` 33 | sudo systemctl daemon-reload 34 | sudo systemctl start gpio-halt.service 35 | ``` 36 | Make the service run every time the system boots: 37 | ``` 38 | sudo systemctl enable gpio-halt.service 39 | ``` 40 | If you want to check the status of the service: 41 | ``` 42 | sudo systemctl status gpio-halt.service 43 | ``` 44 | -------------------------------------------------------------------------------- /gpio-halt.c: -------------------------------------------------------------------------------- 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 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | 65 | // A few globals --------------------------------------------------------- 66 | 67 | char 68 | *progName, // Program name (for error reporting) 69 | sysfs_root[] = "/sys/class/gpio", // Location of Sysfs GPIO files 70 | running = 1; // Signal handler will set to 0 (exit) 71 | int 72 | pin = 21; // Shutdown pin # (override w/argv) 73 | volatile unsigned int 74 | *gpio; // GPIO register table 75 | int 76 | debounceTime = 20; // 20 ms for button debouncing 77 | 78 | 79 | // Some utility functions ------------------------------------------------ 80 | 81 | // Set one GPIO pin attribute through the Sysfs interface. 82 | int pinConfig(char *attr, char *value) { 83 | char filename[50]; 84 | int fd, w, len = strlen(value); 85 | sprintf(filename, "%s/gpio%d/%s", sysfs_root, pin, attr); 86 | if((fd = open(filename, O_WRONLY)) < 0) return -1; 87 | w = write(fd, value, len); 88 | close(fd); 89 | return (w != len); // 0 = success 90 | } 91 | 92 | // Un-export Sysfs pins; don't leave filesystem cruft. Write errors are 93 | // ignored as pins may be in a partially-initialized state. 94 | void cleanup() { 95 | char buf[50]; 96 | int fd; 97 | sprintf(buf, "%s/unexport", sysfs_root); 98 | if((fd = open(buf, O_WRONLY)) >= 0) { 99 | sprintf(buf, "%d", pin); 100 | write(fd, buf, strlen(buf)); 101 | close(fd); 102 | } 103 | } 104 | 105 | // Quick-n-dirty error reporter; print message, clean up and exit. 106 | void err(char *msg) { 107 | printf("%s: %s. Try 'sudo %s'.\n", progName, msg, progName); 108 | cleanup(); 109 | exit(1); 110 | } 111 | 112 | // Interrupt handler -- set global flag to abort main loop. 113 | void signalHandler(int n) { 114 | running = 0; 115 | } 116 | 117 | // Detect Pi board type. Not detailed, just enough for GPIO compatibilty: 118 | // true = Pi 1 Model B revision 1 (some GPIO pin numbers were different) 119 | // false = All other board types 120 | static bool earlyPiDetect(void) { 121 | FILE *fp; 122 | char buf[1024], *ptr; 123 | int n; 124 | bool isEarly = false; // Assume "modern" Pi 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, "boardrev=")) && 132 | (sscanf(&ptr[9], "%x", &n) == 1) && 133 | ((n == 0x02) || (n == 0x03))) { 134 | isEarly = true; // Appears to be an early Pi 135 | break; 136 | } 137 | } 138 | fclose(fp); 139 | } 140 | #else 141 | char s[8]; 142 | if((fp = fopen("/proc/cpuinfo", "r"))) { 143 | while(fgets(buf, sizeof(buf), fp)) { 144 | if((ptr = strstr(buf, "Revision")) && 145 | (sscanf(&ptr[8], " : %x", &n) == 1) && 146 | ((n == 0x02) || (n == 0x03))) { 147 | isEarly = true; // Appears to be an early Pi 148 | break; 149 | } 150 | } 151 | fclose(fp); 152 | } 153 | #endif 154 | 155 | return isEarly; 156 | } 157 | 158 | // Main stuff ------------------------------------------------------------ 159 | 160 | #define GPIO_BASE 0x200000 161 | #define BLOCK_SIZE (4*1024) 162 | #define GPPUD (0x94 / 4) 163 | #define GPPUDCLK0 (0x98 / 4) 164 | 165 | int main(int argc, char *argv[]) { 166 | 167 | char buf[50], // For sundry filenames 168 | c; // Pin input value ('0'/'1') 169 | int fd, // For mmap, sysfs, uinput 170 | timeout = -1, // poll() timeout 171 | pressed; // Last-read pin state 172 | volatile unsigned char shortWait; // Delay counter 173 | struct pollfd p; // GPIO file descriptor 174 | 175 | progName = argv[0]; // For error reporting 176 | signal(SIGINT , signalHandler); // Trap basic signals (exit cleanly) 177 | signal(SIGKILL, signalHandler); 178 | 179 | if(argc > 1) pin = atoi(argv[1]); 180 | // Second argument is the time (in milliseconds) that the button 181 | // should be kept pressed for shutdown to start 182 | if(argc > 2) debounceTime = debounceTime + atoi(argv[2]); 183 | 184 | // If this is a "Revision 1" Pi board (no mounting holes), 185 | // remap certain pin numbers for compatibility. 186 | if(earlyPiDetect()) { 187 | if( pin == 2) pin = 0; 188 | else if(pin == 3) pin = 1; 189 | else if(pin == 27) pin = 21; 190 | } 191 | 192 | // ---------------------------------------------------------------- 193 | // Although Sysfs provides solid GPIO interrupt handling, there's 194 | // no interface to the internal pull-up resistors (this is by 195 | // design, being a hardware-dependent feature). It's necessary to 196 | // grapple with the GPIO configuration registers directly to enable 197 | // the pull-ups. Based on GPIO example code by Dom and Gert van 198 | // Loo on elinux.org 199 | 200 | if((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) 201 | err("Can't open /dev/mem"); 202 | gpio = mmap( // Memory-mapped I/O 203 | NULL, // Any adddress will do 204 | BLOCK_SIZE, // Mapped block length 205 | PROT_READ|PROT_WRITE, // Enable read+write 206 | MAP_SHARED, // Shared with other processes 207 | fd, // File to map 208 | bcm_host_get_peripheral_address() + GPIO_BASE); // -> GPIO registers 209 | close(fd); // Not needed after mmap() 210 | if(gpio == MAP_FAILED) err("Can't mmap()"); 211 | gpio[GPPUD] = 2; // Enable pullup 212 | for(shortWait=150;--shortWait;); // Min 150 cycle wait 213 | gpio[GPPUDCLK0] = 1 << pin; // Set pullup mask 214 | for(shortWait=150;--shortWait;); // Wait again 215 | gpio[GPPUD] = 0; // Reset pullup registers 216 | gpio[GPPUDCLK0] = 0; 217 | (void)munmap((void *)gpio, BLOCK_SIZE); // Done with GPIO mmap() 218 | 219 | // ---------------------------------------------------------------- 220 | // All other GPIO config is handled through the sysfs interface. 221 | 222 | sprintf(buf, "%s/export", sysfs_root); 223 | if((fd = open(buf, O_WRONLY)) < 0) // Open Sysfs export file 224 | err("Can't open GPIO export file"); 225 | sprintf(buf, "%d", pin); 226 | write(fd, buf, strlen(buf)); // Export pin 227 | pinConfig("active_low", "0"); // Don't invert 228 | // Set pin to input, detect rise+fall events 229 | if(pinConfig("direction", "in") || 230 | pinConfig("edge" , "both")) 231 | err("Pin config failed"); 232 | // Get initial pin value 233 | sprintf(buf, "%s/gpio%d/value", sysfs_root, pin); 234 | if((p.fd = open(buf, O_RDONLY)) < 0) 235 | err("Can't access pin value"); 236 | pressed = 0; 237 | if((read(p.fd, &c, 1) == 1) && (c == '0')) pressed = 1; 238 | p.events = POLLPRI; // Set up poll() events 239 | p.revents = 0; 240 | close(fd); // Done exporting 241 | 242 | // ---------------------------------------------------------------- 243 | // Monitor GPIO file descriptor for button events. The poll() 244 | // function watches for GPIO IRQs in this case; it is NOT 245 | // continually polling the pins! Processor load is near zero. 246 | 247 | while(running) { // Signal handler can set this to 0 to exit 248 | // Wait for IRQ on pin (or timeout for button debounce) 249 | if(poll(&p, 1, timeout) > 0) { // If IRQ... 250 | if(p.revents) { // Event received? 251 | // Read current pin state, store in 252 | // 'pressed' state flag, but don't halt 253 | // yet -- must wait for debounce! 254 | lseek(p.fd, 0, SEEK_SET); 255 | read(p.fd, &c, 1); 256 | if(c == '0') pressed = 1; 257 | else if(c == '1') pressed = 0; 258 | p.revents = 0; // Clear flag 259 | } 260 | timeout = debounceTime; // Set timeout for debounce 261 | // Else timeout occurred 262 | } else if(timeout == debounceTime) { // Button debounce timeout 263 | if(pressed) { 264 | (void)system("shutdown -h now"); 265 | running = 0; 266 | } 267 | } 268 | } 269 | 270 | // ---------------------------------------------------------------- 271 | // Clean up 272 | 273 | cleanup(); // Un-export pins 274 | 275 | puts("Done."); 276 | 277 | return 0; 278 | } 279 | --------------------------------------------------------------------------------