|Summary:||On Raspberry Pi, GPIO output pins retain state on halt|
|Product:||Base System||Reporter:||Bob Frazier <bobf>|
|Component:||arm||Assignee:||Luiz Otavio O Souza,+55 (14) 99772-1255 <loos>|
|Severity:||Affects Some People||CC:||aldo, emaste, loos, lyndon|
Description Bob Frazier 2016-08-19 06:13:20 UTC
Running FreeBSD 11.0 RC1 on a Raspbery Pi model 1 'B' When the Raspberry Pi shuts down, the pins that were selected as outputs appear to retain their former state, rather than being disabled (or converted to inputs) on shutdown. This makes it difficult for a particular add-on board (the ATX-Raspi) to detect that the system has halted. As an example, the ATX-Raspi board configures one GPIO as an input, and another as an output, setting the output level to a '1' state when the helper script starts. When the script detects a shutdown or reboot condition, it executes the appropriate command on the Raspberry Pi to either shut down or reboot. This web site explains how the board works: http://hackaday.com/2013/05/19/atx-raspi-is-a-smart-power-source-for-raspberry-pi/ On system halt, however, the GPIO output pin is still configured as an output with a high level. So the system never powers off, since the ATX Raspi board thinks it's still shutting down. To demonstrate it was doing this, I forced a reboot by pressing a key on the serial console. The reboot apparently did a reset all of the GPIO pins, causing the external board to 'detect' a power down, and it then shut down the power within a few seconds [as it is supposed to do]. Unfortunately this workaround isn't acceptable. This board was originally designed to work with a Linux operating system running on the Raspberry Pi. The only problem here appears to be that the GPIO pins are not reset on halt. It may be possible to make this a kernel option that could be programmed via the /boot configuration files, in case resetting them on halt would have any kind of negative impact. Since the Raspberry Pi has no ATX or ACPI interface available to shut down the power programatically, an external board like the ATX Raspi needs some way to easily determine that it is 'safe' to power off the Raspberry Pi. Resetting all of the GPIO pins (or making all of them input pins) on halt would make this work.
Comment 1 lyndon 2016-08-19 22:23:53 UTC
echo '(sleep 10; gpioctl 42 0)&' >> /etc/rc.shutdown.local Replace '42' with the appropriate pin number and adjust the sleep delay as required.
Comment 2 lyndon 2016-08-20 00:44:09 UTC
But in the general case, does it make sense to tri-state all the external GPIO pins on shutdown? I think so, but we can't guarantee that behaviour (kernel panic), so external devices can't rely on it, either.
Comment 3 Bob Frazier 2016-08-20 18:07:05 UTC
the rc.shutdown.local workaround looks interesting. I noticed there's no man page for it, nor is it referenced in the 'man rc.shutdown' page. unfortunately, the 10 second sleep only caused a 'pause', and the external board powered the system off within 5 seconds after the 'gpioctl' switched the pin to an input. After booting single user and running 'fsck -y', I tried something similar using THIS syntax to see if it would help. it didn't. echo '( sleep 30; gpioctl -c 8 IN) &' >> rc.shutdown.local From a shell, a command like that would do what I wanted. however, as I expected, it doesn't work that way in 'rc.shutdown.local' once the system goes into single-user mode (looks like all but thread 0 get killed). I tried it anyway, just to rule it out. I think the main point is that on a normal shutdown/halt-state the I/O pins won't be left in an "un-reset" state. I'm not sure exactly what the Linux kernel gpio driver does in this case. I expect it's shutdown proc resets the IO pins. Similar behavior on microcontrollers varies as well, so there's no actual standard. I downloaded the kernel source so I can have a look at that, see where a patch could be made, report here if I find anything. Unfortunately I'd have to use an external drive and some carefully crafted mounts and symlinks to actually build/install a modified kernel since there's no space for that on a 4G (micro)SD. I do have 2 pi devices, one 'model 1 B', and one 'model 2 B', and the 2 FBSD 11 RC1 images work roughly the same on their corresponding hardware. But neither has enough disk space to load the kernel source and do a 'build/install world/kernel' for me to test it. [and I'm too poor at the moment to go out and buy a bigger SD card].
Comment 4 Bob Frazier 2016-08-20 18:26:53 UTC
looks like the gpioc driver calls 'bus_generic_shutdown' on shutdown. a possible fix MIGHT be to have a gpioc_shutdown proc that first sets all of the relevant pins to an input state, and then calls bus_generic_shutdown. that's assuming that the 'shutdown' proc is being called while in single-user mode prior to halt (which I'm pretty sure it is). I'd have to compile debug stuff to verify it, though (and once again, disk space limitation etc.).
Comment 5 lyndon 2016-08-20 18:35:52 UTC
But again, is switching everything to IN mode the correct thing to do in all cases? This should really be configurable by the user. E.g. sysctl hw.gpio.%d.shutdown_mode = (IN|OUT|OD|PP|TS|PU|PD|II|IO|IGNORE) The flags match those of gpioctl(8), with the addition of IGNORE, which says "leave this pin alone", and would be the default setting for all pins.
Comment 6 Bob Frazier 2016-08-20 19:34:41 UTC
the sysctl variable to adjust shutdown behavior is a very good one. that way it could be controlled by the end user. So then it's just a matter of deciding what the default behavior should be. in any case, if the default is "leave it as-is" and the sysctl var is added, that would be perfect. I'd assign it someplace (maybe in sysctl.conf?) and that would fix everything.
Comment 7 lyndon 2016-08-20 22:32:43 UTC
To clarify, I meant that the hw.gpio.%d.shutdown_mode entries would all initialize to IGNORE at startup. That preserves the existing behaviour.
Comment 8 Luiz Otavio O Souza,+55 (14) 99772-1255 2016-08-21 06:59:02 UTC
I don't like the idea of pollute the sysctls with a setting for everything gpio pin in the system. Especially when usually only a single gpio pin is used to identify the shutdown. What about a new gpio 'device' (something like gpioshutdown) that can be built in the kernel or as a module ? One could build/configure the shutdown pin state of any number of gpio pins this way.
Comment 9 Luiz Otavio O Souza,+55 (14) 99772-1255 2016-08-21 07:09:47 UTC
We cannot assume that is safe to change the GPIO pin stats like that (set all pins to input on shutdown). There are systems with the CPU reset wired to a GPIO pin, GPIO pins that can cause short circuit and so on.
Comment 10 Bob Frazier 2016-08-22 10:24:48 UTC
(from the previous comment) "We cannot assume that is safe to change the GPIO pin stats like that (set all pins to input on shutdown)." Nor can we assume it's potentially UNsafe to do so, because 'reboot' already (effectively) does this. "There are systems with the CPU reset wired to a GPIO pin, GPIO pins that can cause short circuit and so on." Really? [I have to ask which ones, now...] In any case, that's an interesting 'academic' way of looking at things. Basically you have a 'straw man' system that is configured that way, and that would be very unusual (in my opinion). Also, the pins would be re-assigned to INPUT on shutdown, and not be sinking any current. So I can't see how equipment might burn up. Additionally, when you do a reboot, the system DOES appear to reset every GPIO as an input, even if only briefly. So for such a 'straw man' system, where switching an output to an input might fry things, so would doing a reboot, or even a power up. What I'm suggesting here is actually very simple: Do what 'reboot' does, and essentially what a 'cold power up' does, but do it on 'halt' as well. Make all of the things that were configured as 'output' into an 'input' without pull up/down resistors, or even disable them completely [if that's possible or a better choice]. It's apparently what the Linux driver does, and I haven't heard of any RPi hardware frying that's running Raspbian because a shutdown reset the output pins to inputs. I also expect there's not a huge base of RPi FreeBSD users, yet. Making FBSD behave in a more expected manner is more likely to attract a bigger user base.
Comment 11 Luiz Otavio O Souza,+55 (14) 99772-1255 2016-08-23 06:17:14 UTC
Yes, really (pin 3): http://www.dd-wrt.com/phpBB2/viewtopic.php?p=993376 Please, this is not some kind of an academic view... Putting a pin into INPUT is sometimes similar to put the pin in HI-Z (high impedance) mode. This could trigger a reset depending on system design (pullups/down). On reboot the pins go into reset state after the CPU reset, not before. It is very unlikely that a setting a pin to input will fry your board, but there are GPIO pins in your raspberry pi that will fry your board if you change it to an output with$ the wrong polarity. I liked the Lyndon's suggestion: sysctl hw.gpio.%d.shutdown_mode = (IN|OUT|OD|PP|TS|PU|PD|II|IO|IGNORE) Just don't think the sysctl is the right interface for that (think in a system with hundreds of pins). I'm not against this feature, but I want something generic, something that work for every board we support. Do you have something against my suggestion ? (device gpioshutdown)
Comment 12 Bob Frazier 2016-08-23 07:57:36 UTC
I'm hoping for a default that does not require a custom kernel for the RPi. Since the device you showed as an example is NOT a raspberry pi [but simply another device using an ARM or even a BCM28xx CPU] I understand your point. I'm thinking more about the RPi itself, which has its own kernel configs. so if the default RPi kernel has 'whatever device' in it that resets the pins, THAT would work, yes. it also might be interesting if the kernel config allows for specifying the equivalent of that sysctl variable as part of the kernel config; i.e., the default I/O pin config on startup AND shutdown [with the default being 'input']. relevant code seems to be here: arm/broadcom/bcm2835/bcm2835_gpio.c this is apparently included for both the RPI-B and RPI2 kernel configs, even though they have a different number of GPIO pins. so now I have to wonder if it may be possible to specify a 'kernel option' to do the GPIO reset on shutdown, maybe "option GPIO_RESET_ON_SHUTDOWN" or similar then there are also THESE files that appear to define the reserved GPIOs: boot/fdt/dts/arm/rpi.dts boot/fdt/dts/arm/rpi2.dts with that (unless I misunderstand what they're doing) there appears to be a mechanism for defining I/O pins that aren't going to be mucked with during a reboot or [in this case] a shutdown. So I'm guessing that this file would be appropriately updated for anything using an I/O pin as a CPU reset [for example] like the DDWRT example. I'm glad this prompted me to research more, though. I'm seeing changes to the way kernel configs are done from what I'm used to seeing.
Comment 13 Bob Frazier 2016-08-29 21:35:40 UTC
I'm going to investigate adding a 'gpioshutdown' device. I'll start with a dynamic kernel module, go from there. I'll attach a tarball if I get something working, rather than putting it into ports, hoping to get it into base instead. I did a kernel module 'comms/uarduno' that helped a lot with FBSD through 9 but I think 10 (and definitely 11) USB serial drivers handle the Arduino's device type properly. But rather than go THAT route, I'd rather submit the code to fix this problem, in case I get it done more quickly, then HOPEFULLY 11-release will have it built-in.
Comment 14 Bob Frazier 2016-08-31 19:32:45 UTC
doing some analysis, discovered that GPIO pins that were configured during normal operation as an output are marked as being 'input' by the driver during the shutdown process, but are NOT actually INPUT pins. So at some point prior to the shutdown message 'MOD_SHUTDOWN', the pin's internal flags were apparently set to mark it as 'GPIO_PIN_INPUT'. 'MOD_SHUTDOWN' occurs some time during the final shutdown phase 'shutdown_final' (I had an event handler registered with printf's to watch it all). If you schedule a handler as 'EVENTHANDLER_PRI_LAST' its message is printed out AFTER the 'MOD_SHUTDOWN' callback event. This all occurs after the sync, and after the 'uptime' message is displayed. It's possible that something between these two points (a bus shutdown, perhaps?) causes a disconnect between the GPIO pins and the internal flags, or even resets all of the internal flags without resetting the GPIO pins. So, I have to wonder. 1. Was it INTENDED to convert all of the outputs into inputs? Otherwise, why were the flags being changed without changing the actual pins? 2. Will it be necessary to modify the appropriate gpio driver to correct this "bug" ? Or is it a 'feature'? I'll research this some more. If changing the point at which the pins are reset fixes the problem, I'll just submit working code as a sample patch. Otherwise I'll have to dive into the whole GPIO handling system and hopefully won't break anything that this 'feature' is causing...
Comment 15 Bob Frazier 2016-08-31 23:34:13 UTC
Created attachment 174273 [details] proposed kernel driver 'gpioshutdown', preliminary I am submitting a sample kernel module that, when dynamically loaded via 'kldload', will reset all of the GPIO pins that are either marked input or output to an input state [without a pull-up/down resistor], right before system halt. The module is called 'gpioshutdown' and I'm submitting as a unified diff file. Sorry if I didn't use hard tabs. it compiles and 'works'. this is derived from 'mostly debug' code I was using to determine when the best time to reset the pins was, and also to check pin state. It has a minor flaw, when you use a serial console, in that it resets the I/O pins that are being used for the serial port. NORMALLY the serial port I/O pins are marked as neither input NOR output, unless the user decides to re-purpose them. Unfortunately, at a point between where the shutdown begins, and where the MOD_SHUTDOWN event happens, all of the non-reserved pins appear to be marked as 'inputs' in the status array within the gpio driver. However, the actual pin state is not properly reflected. This includes the 2 I/O pins being used for the serial port. To compensate for this, I re-set all pins marked either as 'input' or 'output' to 'input' pins with no pull-up/down resistor. Unfortunately, this causes the serial console to be unable to print messages. It does not affect a reboot, though, so if you reboot while the module is loaded, you just lose part of the message before the bootloader fixes things. Additionally, it's set up (right now) as a dynamically loadable module. I have not tested it as a "built into the kernel" module but I see no reason why it won't work. As such it is a 'partial fix'. it also does not fix what I believe is at the heart of the matter, the arbitrary resetting of the status array within the gpio driver to 'input' for all of the non-reserved I/O pins, without actually changing the pins themselves. This needs a serious review, I think, because neither this driver nor the current behavior is correct.
Comment 16 Bob Frazier 2016-09-01 07:36:18 UTC
the serial port I/O pin assignment 'confusion' seems to only affect reboot, but not shutdown. so if I do a shutdown, I see all of the kernel messages and the 'press any key to boot' message. If I do a reboot, only part of the messages show up. So it appears that the GPIO pins associated with the serial port may be getting 'reset' differently for poweroff vs reboot. This might be a hint as to where in the code this is happening, a particular event for a particular driver (for example). I'm mentioning this in case someone already knows the answer. It's one of those kinds of indications that might point directly at the problem, if I were more familiar with the internal workings.
Comment 17 Aldo 2017-09-29 12:42:54 UTC
PLEASE TRY TO KEEP THINGS SIMPLE! What we need for real life application is something like this. 1)you run "sudo shutdown now" and system halts a)you touched nothing, everything goes like now (GPIO pins retain state on halt) you have backwards compatibility: all past machines are safe and we are all happy b)you edited a conf file; you did setup whatever (we have no preferences) and you can have a behavior like this (for example) GPIO pin GEN1 output goes to 0 immediately after halt (example: this will be connected to a circuit that cuts the power, active LOW) As said above it is likely that this feature will be useful for one pin only, but you could provide something more generic. The lack of this feature cause problems, as in battery powered application you should know when the system is Halted if you don't just want to make a hard and rude power off (or do tricks). Thank you for your patience.
Comment 18 Bob Frazier 2017-09-29 17:49:02 UTC
keeping this simple - yes, I agree. If the problem is compatibility with every other device out there, then why not just have "an extra module" for RPi in the kernel configs? So that way, the dd-wrt project that was referenced in this discussion would NOT be affected in any way. There is no need to generically change EVERYTHING that uses an ARM processor, or more specifically, the broadcom ARM processors that are used by RPi. However, there also needs to be "a way" to identify that the board shut down for headless systems. Otherwise you're going to end up with a corrupted file system if you "just guess" that the device actually shut down, when you power it off. If FreeBSD on an RPi is to be considered as a SERIOUS alternative to using Linux, or an RTOS [and I would like this to happen, by the way] then a kernel module similar to the one I posted ('gpioshutdown') needs to be added to the default kernel config for RPi, at the very least to be compatible with Linux, as well as any hardware that was designed to work with a Linux OS in this way (such as the atx-raspi that I mentioned earlier). Safe shutdown for subsequent power off is a very very very important feature for any device that runs on batteries, as others have mentioned. When the battery is dead, you want to safely shut down, and not be forced to use a serial port to fsck the file system whenever the shutdown was improper. Anyway, I'd like this requested change NOT to die, please. So far it looks like I'm going to have to build custom kernels for every version of FreeBSD, indefinitely. Either that, or make 'gpioshutdown' into a port.
Comment 19 Bob Frazier 2018-03-06 20:36:58 UTC
It has been quite a while since I submitted this bug along with the proposed fix. As a result, I'm just going to add a port for the gpioshutdown kernel module to resolve this. I have already tested the 'work in progress' version on an RPi Model 2 running a slightly older (11.0) kernel, and plan on submitting a new port for the 'gpioshutdown' kernel module very soon. The (tentative) port name is misc/raspberrypi-gpioshutdown and should only build for the armv6 architecture. It's effectively the same as the patch files that are attached to the bug report. I just need to make sure it passes all of the necessary lint and functionality tests in order to build/run correctly as a port before I submit it.
Comment 20 Bob Frazier 2018-03-19 20:43:03 UTC
submitted new port that should correct this bug: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=226787 I recommend installing this port as the corrective action for the original bug report, unless someone else thinks it should remain open. The port also allows you to specifically choose an 'LED' pin and its polarity, which will be placed in an 'off' state when it's ok to power off. It is also set to 'on' just before performing the disk sync sequence, although the included rc.d script will set it to 'on' whenever you load the driver with the script. The defaults in the script choose the 'pwr' LED on a Raspberry Pi model 2, and the 'ok' led on a Raspberry Pi model 1B.