> The attached diff adds the ability to pulse a pin with a set frequency > and duty cycle. A new gpio(4) ioctl, "GPIOPULSE", and a new gpioctl(8) > command line option "pulse" are added. > > Whereas the gpioctl(8) command line utility accepts the freqeuncy in > hertz and the duty cycle in percent, these values are converted to two > "struct timeval"s for the ioctl call. gpio(4) accepts an "on" and "off" > period as "struct timeval" which are then converted to ticks using > tvtohz(9). > > The pulsing in software is realised using callout(9)s, if the underlying > hardware signals that it can pulse the pin in hardware, the pin is just > set to GPIO_PIN_HIGH and no callouts are used. There is no way at the > moment to program the "on" and "off" periods for pins that pulse in > hardware (implementing that is left as an exercise to the reader...)
Find attached revised version that uses only one callout and clears pending callouts on device detach. I also removed a few superflous tests, as noticed by rmind. If you have any GPIO drivers as modules, e.g. gpiosim(4), make sure to rebuild them as internal data structures have changed. I can tell from experience^Wpain that failing to do so leads to panics... Comments?
Index: sys/sys/gpio.h =================================================================== RCS file: /cvsroot/src/sys/sys/gpio.h,v retrieving revision 1.8 diff -u -p -r1.8 gpio.h --- sys/sys/gpio.h 23 Jun 2011 00:46:37 -0000 1.8 +++ sys/sys/gpio.h 21 Aug 2011 13:28:36 -0000 @@ -20,9 +20,12 @@ #ifndef _SYS_GPIO_H_ #define _SYS_GPIO_H_ +#include <sys/time.h> + /* GPIO pin states */ #define GPIO_PIN_LOW 0x00 /* low level (logical 0) */ #define GPIO_PIN_HIGH 0x01 /* high level (logical 1) */ +#define GPIO_PIN_PULSE 0x02 /* pulsing, or-ed with state */ /* Max name length of a pin */ #define GPIOMAXNAME 64 @@ -51,25 +54,33 @@ struct gpio_info { /* GPIO pin request (read/write/toggle) */ struct gpio_req { - char gp_name[GPIOMAXNAME]; /* pin name */ - int gp_pin; /* pin number */ - int gp_value; /* value */ + char gp_name[GPIOMAXNAME]; /* pin name */ + int gp_pin; /* pin number */ + int gp_value; /* value */ +}; + +/* GPIO pulse request */ +struct gpio_pulse { + char gp_name[GPIOMAXNAME]; /* pin name */ + int gp_pin; /* pin number */ + struct timeval gp_pulse_on; /* "on" period */ + struct timeval gp_pulse_off; /* "off" period */ }; /* GPIO pin configuration */ struct gpio_set { - char gp_name[GPIOMAXNAME]; - int gp_pin; - int gp_caps; - int gp_flags; - char gp_name2[GPIOMAXNAME]; /* new name */ + char gp_name[GPIOMAXNAME]; + int gp_pin; + int gp_caps; + int gp_flags; + char gp_name2[GPIOMAXNAME]; /* new name */ }; /* Attach/detach device drivers that use GPIO pins */ struct gpio_attach { - char ga_dvname[16]; /* device name */ - int ga_offset; /* pin number */ - u_int32_t ga_mask; /* binary mask */ + char ga_dvname[16]; /* device name */ + int ga_offset; /* pin number */ + u_int32_t ga_mask; /* binary mask */ }; /* GPIO pin control (old API) */ @@ -81,8 +92,8 @@ struct gpio_pin_ctl { /* GPIO pin operation (read/write/toggle) (old API) */ struct gpio_pin_op { - int gp_pin; /* pin number */ - int gp_value; /* value */ + int gp_pin; /* pin number */ + int gp_value; /* value */ }; #define GPIOINFO _IOR('G', 0, struct gpio_info) @@ -101,5 +112,6 @@ struct gpio_pin_op { #define GPIOTOGGLE _IOWR('G', 9, struct gpio_req) #define GPIOATTACH _IOWR('G', 10, struct gpio_attach) #define GPIODETACH _IOWR('G', 11, struct gpio_attach) +#define GPIOPULSE _IOWR('G', 12, struct gpio_pulse) #endif /* !_SYS_GPIO_H_ */ Index: sys/dev/gpio/gpiovar.h =================================================================== RCS file: /cvsroot/src/sys/dev/gpio/gpiovar.h,v retrieving revision 1.11 diff -u -p -r1.11 gpiovar.h --- sys/dev/gpio/gpiovar.h 12 Aug 2011 08:00:52 -0000 1.11 +++ sys/dev/gpio/gpiovar.h 21 Aug 2011 13:28:36 -0000 @@ -35,18 +35,22 @@ typedef struct gpio_chipset_tag { /* GPIO pin description */ typedef struct gpio_pin { - int pin_num; /* number */ - int pin_caps; /* capabilities */ - int pin_flags; /* current configuration */ - int pin_state; /* current state */ - int pin_mapped; /* is mapped */ + int pin_num; /* number */ + int pin_caps; /* capabilities */ + int pin_flags; /* current configuration */ + int pin_state; /* current state */ + int pin_mapped; /* is mapped */ + callout_t pin_pulse; /* for pulsing */ + int pin_ticks_on; /* "on" period */ + int pin_ticks_off; /* "off" period */ + gpio_chipset_tag_t pin_gc; /* reference the controller */ } gpio_pin_t; /* Attach GPIO framework to the controller */ struct gpiobus_attach_args { - gpio_chipset_tag_t gba_gc; /* underlying controller */ + gpio_chipset_tag_t gba_gc; /* underlying controller */ gpio_pin_t *gba_pins; /* pins array */ - int gba_npins; /* total number of pins */ + int gba_npins; /* total number of pins */ }; int gpiobus_print(void *, const char *); Index: sys/dev/gpio/gpio.c =================================================================== RCS file: /cvsroot/src/sys/dev/gpio/gpio.c,v retrieving revision 1.35 diff -u -p -r1.35 gpio.c --- sys/dev/gpio/gpio.c 12 Aug 2011 08:00:52 -0000 1.35 +++ sys/dev/gpio/gpio.c 21 Aug 2011 13:28:37 -0000 @@ -26,12 +26,14 @@ __KERNEL_RCSID(0, "$NetBSD: gpio.c,v 1.3 */ #include <sys/param.h> +#include <sys/callout.h> #include <sys/systm.h> #include <sys/conf.h> #include <sys/device.h> #include <sys/fcntl.h> #include <sys/ioctl.h> #include <sys/gpio.h> +#include <sys/kernel.h> #include <sys/vnode.h> #include <sys/kmem.h> #include <sys/queue.h> @@ -73,6 +75,7 @@ static int gpio_detach(device_t, int); static int gpio_search(device_t, cfdata_t, const int *, void *); static int gpio_print(void *, const char *); static int gpio_pinbyname(struct gpio_softc *, char *); +static void gpio_pulse(void *); /* Old API */ static int gpio_ioctl_oapi(struct gpio_softc *, u_long, void *, int, @@ -165,7 +168,17 @@ gpio_attach(device_t parent, device_t se static int gpio_detach(device_t self, int flags) { - int rc; + struct gpio_softc *sc; + int pin, rc; + + sc = device_private(self); + + for (pin = 0; pin < sc->sc_npins; pin++) + if (sc->sc_pins[pin].pin_state & GPIO_PIN_PULSE) { + callout_stop(&sc->sc_pins[pin].pin_pulse); + callout_destroy(&sc->sc_pins[pin].pin_pulse); + sc->sc_pins[pin].pin_state &= ~GPIO_PIN_PULSE; + } if ((rc = config_detach_children(self, flags)) != 0) return rc; @@ -382,6 +395,26 @@ gpio_pinbyname(struct gpio_softc *sc, ch return -1; } +static void +gpio_pulse(void *arg) +{ + struct gpio_pin *pin; + + pin = (struct gpio_pin *)arg; + if ((pin->pin_state & GPIO_PIN_PULSE) == 0) + return; + + if (pin->pin_state & GPIO_PIN_HIGH) { + gpiobus_pin_write(pin->pin_gc, pin->pin_num, GPIO_PIN_LOW); + pin->pin_state &= ~GPIO_PIN_HIGH; + callout_schedule(&pin->pin_pulse, pin->pin_ticks_off); + } else { + gpiobus_pin_write(pin->pin_gc, pin->pin_num, GPIO_PIN_HIGH); + pin->pin_state |= GPIO_PIN_HIGH; + callout_schedule(&pin->pin_pulse, pin->pin_ticks_on); + } +} + int gpioioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) { @@ -392,6 +425,7 @@ gpioioctl(dev_t dev, u_long cmd, void *d struct gpio_attach_args ga; struct gpio_dev *gdev; struct gpio_req *req; + struct gpio_pulse *pulse; struct gpio_name *nm; struct gpio_set *set; device_t dv; @@ -427,11 +461,9 @@ gpioioctl(dev_t dev, u_long cmd, void *d case GPIOREAD: req = (struct gpio_req *)data; - if (req->gp_name[0] != '\0') { + if (req->gp_name[0] != '\0') pin = gpio_pinbyname(sc, req->gp_name); - if (pin == -1) - return EINVAL; - } else + else pin = req->gp_pin; if (pin < 0 || pin >= sc->sc_npins) @@ -451,11 +483,9 @@ gpioioctl(dev_t dev, u_long cmd, void *d req = (struct gpio_req *)data; - if (req->gp_name[0] != '\0') { + if (req->gp_name[0] != '\0') pin = gpio_pinbyname(sc, req->gp_name); - if (pin == -1) - return EINVAL; - } else + else pin = req->gp_pin; if (pin < 0 || pin >= sc->sc_npins) @@ -473,23 +503,75 @@ gpioioctl(dev_t dev, u_long cmd, void *d if (value != GPIO_PIN_LOW && value != GPIO_PIN_HIGH) return EINVAL; + if (sc->sc_pins[pin].pin_state & GPIO_PIN_PULSE) { + callout_stop(&sc->sc_pins[pin].pin_pulse); + callout_destroy(&sc->sc_pins[pin].pin_pulse); + sc->sc_pins[pin].pin_state &= ~GPIO_PIN_PULSE; + } gpiobus_pin_write(gc, pin, value); /* return old value */ req->gp_value = sc->sc_pins[pin].pin_state; /* update current value */ sc->sc_pins[pin].pin_state = value; break; + case GPIOPULSE: + if ((flag & FWRITE) == 0) + return EBADF; + + pulse = (struct gpio_pulse *)data; + + if (pulse->gp_name[0] != '\0') + pin = gpio_pinbyname(sc, pulse->gp_name); + else + pin = pulse->gp_pin; + + if (pin < 0 || pin >= sc->sc_npins) + return EINVAL; + + if (sc->sc_pins[pin].pin_mapped) + return EBUSY; + + if (!(sc->sc_pins[pin].pin_flags & GPIO_PIN_SET) && + kauth_authorize_device(cred, KAUTH_DEVICE_GPIO_PINSET, + NULL, NULL, NULL, NULL)) + return EPERM; + + if (sc->sc_pins[pin].pin_flags & GPIO_PIN_PULSATE) { + gpiobus_pin_write(gc, pin, GPIO_PIN_HIGH); + sc->sc_pins[pin].pin_state = GPIO_PIN_PULSE; + return 0; + } + + if (sc->sc_pins[pin].pin_state & GPIO_PIN_PULSE) { + callout_stop(&sc->sc_pins[pin].pin_pulse); + callout_destroy(&sc->sc_pins[pin].pin_pulse); + } + sc->sc_pins[pin].pin_gc = gc; + callout_init(&sc->sc_pins[pin].pin_pulse, 0); + + sc->sc_pins[pin].pin_ticks_on = tvtohz(&pulse->gp_pulse_on); + sc->sc_pins[pin].pin_ticks_off = tvtohz(&pulse->gp_pulse_off); + if (sc->sc_pins[pin].pin_ticks_on == 0 + || sc->sc_pins[pin].pin_ticks_off == 0) { + sc->sc_pins[pin].pin_ticks_on = hz / 2; + sc->sc_pins[pin].pin_ticks_off = hz / 2; + } + callout_setfunc(&sc->sc_pins[pin].pin_pulse, gpio_pulse, + &sc->sc_pins[pin]); + gpiobus_pin_write(gc, pin, GPIO_PIN_HIGH); + sc->sc_pins[pin].pin_state = GPIO_PIN_HIGH | GPIO_PIN_PULSE; + callout_schedule(&sc->sc_pins[pin].pin_pulse, + sc->sc_pins[pin].pin_ticks_on); + break; case GPIOTOGGLE: if ((flag & FWRITE) == 0) return EBADF; req = (struct gpio_req *)data; - if (req->gp_name[0] != '\0') { + if (req->gp_name[0] != '\0') pin = gpio_pinbyname(sc, req->gp_name); - if (pin == -1) - return EINVAL; - } else + else pin = req->gp_pin; if (pin < 0 || pin >= sc->sc_npins) @@ -574,12 +656,11 @@ gpioioctl(dev_t dev, u_long cmd, void *d set = (struct gpio_set *)data; - if (set->gp_name[0] != '\0') { + if (set->gp_name[0] != '\0') pin = gpio_pinbyname(sc, set->gp_name); - if (pin == -1) - return EINVAL; - } else + else pin = set->gp_pin; + if (pin < 0 || pin >= sc->sc_npins) return EINVAL; flags = set->gp_flags; @@ -629,11 +710,9 @@ gpioioctl(dev_t dev, u_long cmd, void *d return EPERM; set = (struct gpio_set *)data; - if (set->gp_name[0] != '\0') { + if (set->gp_name[0] != '\0') pin = gpio_pinbyname(sc, set->gp_name); - if (pin == -1) - return EINVAL; - } else + else pin = set->gp_pin; if (pin < 0 || pin >= sc->sc_npins) Index: share/man/man4/gpio.4 =================================================================== RCS file: /cvsroot/src/share/man/man4/gpio.4,v retrieving revision 1.18 diff -u -p -r1.18 gpio.4 --- share/man/man4/gpio.4 22 Mar 2010 18:58:31 -0000 1.18 +++ share/man/man4/gpio.4 21 Aug 2011 13:28:37 -0000 @@ -15,7 +15,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd September 27, 2009 +.Dd August 21, 2011 .Dt GPIO 4 .Os .Sh NAME @@ -123,6 +123,31 @@ Toggles the pin output value, i.e. chang .Fa gp_value field is ignored and on return contains the old pin state. .Pp +.It Dv GPIOPULSE (struct gpio_pulse) +Starts pulsing the pin: +.Bd -literal +/* GPIO pulse request */ +struct gpio_pulse { + char gp_name[GPIOMAXNAME]; /* pin name */ + int gp_pin; /* pin number */ + struct timeval gp_pulse_on; /* "on" period */ + struct timeval gp_pulse_off; /* "off" period */ +}; +.Ed +.Pp +The +.Fa gp_name +or +.Fa gp_pin +field must be set before calling. +If +.Fa gp_pulse_on +or +.Fa gp_pulse_off +is set to zero, a default of +.Xr hz 9 / 2 +is assumed for both periods. +.Pp .It Dv GPIOSET (struct gpio_set) Changes pin configuration flags with the new ones provided in the .Fa gpio_set Index: usr.sbin/gpioctl/gpioctl.8 =================================================================== RCS file: /cvsroot/src/usr.sbin/gpioctl/gpioctl.8,v retrieving revision 1.8 diff -u -p -r1.8 gpioctl.8 --- usr.sbin/gpioctl/gpioctl.8 12 Aug 2011 08:02:33 -0000 1.8 +++ usr.sbin/gpioctl/gpioctl.8 21 Aug 2011 13:28:37 -0000 @@ -1,6 +1,6 @@ .\" $NetBSD: gpioctl.8,v 1.8 2011/08/12 08:02:33 mbalmer Exp $ .\" -.\" Copyright (c) 2009, 2010 Marc Balmer <m...@msys.ch> +.\" Copyright (c) 2009, 2010, 2011 Marc Balmer <m...@msys.ch> .\" Copyright (c) 2004 Alexander Yurchenko <gra...@openbsd.org> .\" .\" Permission to use, copy, modify, and distribute this software for any @@ -15,7 +15,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd September 25, 2009 +.Dd August 21, 2011 .Dt GPIOCTL 8 .Os .Sh NAME @@ -48,6 +48,12 @@ detach .Op Fl q .Ar device .Ar pin +pulse +.Op Ar frequency Op Ar duty cycle +.Nm gpioctl +.Op Fl q +.Ar device +.Ar pin set .Op Ar flags .Op Ar name @@ -92,7 +98,7 @@ To write to a pin, a value must be speci .Ar pin number. Values can be either 0 or 1. -A value of 2 has a special meaning: it +A value of 2 .Dq toggles the pin, i.e. changes its state to the opposite. Instead of the numerical values, the word @@ -102,6 +108,19 @@ or .Ar toggle can be used. .Pp +To +.Dq pulse +a pin, use the pulse command line option with an optional frequency value +in hertz and an optional duty cycle in percent. +If no frequency is specified, 1 Hz is assumed. +If no duty cycle is specified, 50% are assumed. +If the underlying hardware is not capable of pulsing in hardware, +pulsing is done in software using the +.Xr callout 9 +facility. +The frequency and duty cycle arguments are ignored for pins that are able to +pulse in hardware. +.Pp Only pins that have been configured at securelevel 0, typically during system startup, are accessible once the securelevel has been raised. Pins can be given symbolic names for easier use. @@ -207,5 +226,5 @@ The .Nm program was written by .An Alexander Yurchenko Aq gra...@openbsd.org . -Device attachment was added by +Device attachment and pulsing was added by .An Marc Balmer Aq m...@msys.ch . Index: usr.sbin/gpioctl/gpioctl.c =================================================================== RCS file: /cvsroot/src/usr.sbin/gpioctl/gpioctl.c,v retrieving revision 1.10 diff -u -p -r1.10 gpioctl.c --- usr.sbin/gpioctl/gpioctl.c 12 Aug 2011 08:06:23 -0000 1.10 +++ usr.sbin/gpioctl/gpioctl.c 21 Aug 2011 13:28:37 -0000 @@ -32,6 +32,7 @@ #include <paths.h> #include <stdio.h> #include <stdlib.h> +#include <time.h> #include <string.h> #include <unistd.h> @@ -43,6 +44,7 @@ int quiet = 0; static void getinfo(void); static void gpioread(int, char *); static void gpiowrite(int, char *, int); +static void gpiopulse(int, char *, double, double); static void gpioset(int pin, char *name, int flags, char *alias); static void gpiounset(int pin, char *name); static void devattach(char *, int, u_int32_t); @@ -74,6 +76,7 @@ int main(int argc, char *argv[]) { const struct bitstr *bs; + double freq, dc; int pin, ch, n, fl = 0, value = 0; const char *errstr; char *ep; @@ -99,6 +102,8 @@ main(int argc, char *argv[]) usage(); dev = argv[0]; + freq = dc = 0.0; + if (strncmp(_PATH_DEV, dev, sizeof(_PATH_DEV) - 1)) { (void)snprintf(devn, sizeof(devn), "%s%s", _PATH_DEV, dev); dev = devn; @@ -140,12 +145,12 @@ main(int argc, char *argv[]) devdetach(argv[2]); } else { char *nm = NULL; - + /* expecting a pin number or name */ pin = strtonum(argv[1], 0, INT_MAX, &errstr); if (errstr) nm = argv[1]; /* try named pin */ - if (argc > 2) { + if (argc > 2) { if (!strcmp(argv[2], "set")) { for (n = 3; n < argc; n++) { for (bs = pinflags; bs->string != NULL; @@ -162,14 +167,24 @@ main(int argc, char *argv[]) gpioset(pin, nm, fl, nam); } else if (!strcmp(argv[2], "unset")) { gpiounset(pin, nm); + } else if (!strcmp(argv[2], "pulse")) { + if (argc == 4) { + freq = atof(argv[3]); + dc = 50.0; + } else if (argc == 5) { + freq = atof(argv[3]); + dc = atof(argv[4]); + } else + freq = dc = 0.0; + gpiopulse(pin, nm, freq, dc); } else { value = strtonum(argv[2], INT_MIN, INT_MAX, &errstr); if (errstr) { if (!strcmp(argv[2], "on")) - value = 1; + value = GPIO_PIN_HIGH; else if (!strcmp(argv[2], "off")) - value = 0; + value = GPIO_PIN_LOW; else if (!strcmp(argv[2], "toggle")) value = 2; else @@ -236,8 +251,9 @@ gpiowrite(int pin, char *gp_name, int va strlcpy(req.gp_name, gp_name, sizeof(req.gp_name)); else req.gp_pin = pin; - req.gp_value = (value == 0 ? GPIO_PIN_LOW : GPIO_PIN_HIGH); - if (value < 2) { + + if (value == GPIO_PIN_HIGH || value == GPIO_PIN_LOW) { + req.gp_value = value; if (ioctl(devfd, GPIOWRITE, &req) == -1) err(EXIT_FAILURE, "GPIOWRITE"); } else { @@ -257,6 +273,62 @@ gpiowrite(int pin, char *gp_name, int va } static void +gpiopulse(int pin, char *gp_name, double freq, double dc) +{ + struct gpio_pulse pulse; + suseconds_t period, on, off; + + if (freq < 0.0 || (dc < 0.0 || dc >= 100.0)) + errx(EXIT_FAILURE, "%.f Hz, %.f%% duty cycle: invalid value", + freq, dc); + + memset(&pulse, 0, sizeof(pulse)); + if (gp_name != NULL) + strlcpy(pulse.gp_name, gp_name, sizeof(pulse.gp_name)); + else + pulse.gp_pin = pin; + + if (freq > 0.0 && dc > 0.0) { + period = 1000000 / freq; + on = period * dc / 100; + off = period - on; + + if (on >= 1000000) { + pulse.gp_pulse_on.tv_sec = on / 1000000; + on -= pulse.gp_pulse_on.tv_sec * 1000000; + pulse.gp_pulse_on.tv_usec = on; + } else { + pulse.gp_pulse_on.tv_sec = 0; + pulse.gp_pulse_on.tv_usec = on; + } + if (off >= 1000000) { + pulse.gp_pulse_off.tv_sec = off / 1000000; + off -= pulse.gp_pulse_off.tv_sec * 1000000; + pulse.gp_pulse_off.tv_usec = off; + } else { + pulse.gp_pulse_off.tv_sec = 0; + pulse.gp_pulse_off.tv_usec = off; + } + } else { /* gpio(4) defaults */ + freq = 1.0; + dc = 50.0; + } + + if (ioctl(devfd, GPIOPULSE, &pulse) == -1) + err(EXIT_FAILURE, "GPIOPULSE"); + + if (quiet) + return; + + if (gp_name) + printf("pin %s: pulse at %.f Hz with a %.f%% duty cylce\n", + gp_name, freq, dc); + else + printf("pin %d: pulse at %.f Hz with a %.f%% duty cylce\n", + pin, freq, dc); +} + +static void gpioset(int pin, char *name, int fl, char *alias) { struct gpio_set set; @@ -345,6 +417,8 @@ usage(void) progname = getprogname(); fprintf(stderr, "usage: %s [-q] device [pin] [0 | 1 | 2 | " "on | off | toggle]\n", progname); + fprintf(stderr, "usage: %s [-q] device pin pulse [frequency " + "[duty cycle]]\n", progname); fprintf(stderr, " %s [-q] device pin set [flags] [name]\n", progname); fprintf(stderr, " %s [-q] device pin unset\n", progname);