FreeBSD

Understanding the Raspberry Pi – FreeBSD gpio device

Recently I found myself wanting to understand how I2C for FreeBSD ARM, on the Pi works.  The first step is to understand the GPIO implementation on Pi, since on a Pi, I2C is implemented on the GPIO. So, here’s the basic summary. Firstly, the FreeBSD source browser is here. In the directory /sys/ is the kernel code for all platforms, and in /sys/sys/ is the core kernel code.  In /sys/sys/gpio.h, you’ll find the header which declares the GPIO API for FreeBSD, which must be supported by all FreeBSD GPIO devices on all platforms. Here’s the important parts: /* GPIO pin states */ #define GPIO_PIN_LOW 0x00 /* low level (logical 0) */ #define GPIO_PIN_HIGH 0x01 /* high level (logical 1) */ /* Max name length of a pin */ #define GPIOMAXNAME 64 /* GPIO pin configuration flags */ #define GPIO_PIN_INPUT 0x0001 /* input direction */ #define GPIO_PIN_OUTPUT 0x0002 /* output direction */ #define GPIO_PIN_OPENDRAIN 0x0004 /* open-drain output */ #define GPIO_PIN_PUSHPULL 0x0008 /* push-pull output */ #define GPIO_PIN_TRISTATE 0x0010 /* output disabled */ #define GPIO_PIN_PULLUP 0x0020 /* internal pull-up enabled */ #define GPIO_PIN_PULLDOWN 0x0040 /* internal pull-down enabled */ #define GPIO_PIN_INVIN 0x0080 /* invert input */ #define GPIO_PIN_INVOUT 0x0100 /* invert output */ #define GPIO_PIN_PULSATE 0x0200 /* pulsate in hardware */ struct gpio_pin { uint32_t gp_pin; /* pin number */ char gp_name[GPIOMAXNAME]; /* human-readable name */ uint32_t gp_caps; /* capabilities */ uint32_t gp_flags; /* current flags */ }; /* GPIO pin request (read/write/toggle) */ struct gpio_req { uint32_t gp_pin; /* pin number */ uint32_t gp_value; /* value */ }; /* * ioctls */ #define GPIOMAXPIN _IOR(‘G’, 0, int) #define GPIOGETCONFIG _IOWR(‘G’, 1, struct gpio_pin) #define GPIOSETCONFIG _IOW(‘G’, 2, struct gpio_pin) #define GPIOGET _IOWR(‘G’, 3, struct gpio_req) #define GPIOSET _IOW(‘G’, 4, struct gpio_req) #define GPIOTOGGLE _IOWR(‘G’, 5, struct gpio_req) So, this defines what a GPIO device looks like to FreeBSD and the ioctls it supports, on all platforms. For the ARM kernel, the source code is in /sys/arm/, and for Raspberry Pi, which based on a Broadcom BCM 2835 chip the kernel sources are at /sys/arm/broadcom/bcm2835/. In /sys/arm/broadcom/bcm2835/bcm2835_gpio.c, you’ll find the implementation which exposes the GPIO device, for a Raspberry Pi.  You should notice that the header “gpio_if.h” is included.  This is a generated file, and the source is in /sys/dev/gpio_if.m.   The important parts of this are here: INTERFACE gpio; # # Get total number of pins # METHOD int pin_max { device_t dev; int *npins; }; # # Set value of pin specifed by pin_num # METHOD int pin_set { device_t dev; uint32_t pin_num; uint32_t pin_value; }; # # Get value of pin specifed by pin_num # METHOD int pin_get { device_t dev; uint32_t pin_num; uint32_t *pin_value; }; # # Toggle value of pin specifed by pin_num # METHOD int pin_toggle { device_t dev; uint32_t pin_num; }; # # Get pin capabilities # METHOD int pin_getcaps { device_t dev; uint32_t pin_num; uint32_t *caps; }; # # Get pin flags # METHOD int pin_getflags { device_t dev; uint32_t pin_num; uint32_t *flags; }; # # Get pin name # METHOD int pin_getname { device_t dev; uint32_t pin_num; char *name; }; # # Set current configuration and capabilities # METHOD int pin_setflags { device_t dev; uint32_t pin_num; uint32_t flags; }; An important piece of code to notice in “bcm2835_gpio.c” is the device driver method “probe”: static int bcm_gpio_probe(device_t dev) { if (!ofw_bus_is_compatible(dev, “broadcom,bcm2835-gpio”)) return (ENXIO); device_set_desc(dev, “BCM2708/2835 GPIO controller”); return (BUS_PROBE_DEFAULT); } This is the method FreeBSD calls to ask the driver if a specific piece of hardware is appropriate for that driver.  The ARM GPIO driver looks for “broadcom,bcm2835-gpio”, which is the exact name defined in the Raspberry Pi FDT. Another method which  if the “attach” method, which initializes the driver and adds it to the bus.  This line gets a “bcm_gpio_softc” struct from the device_t passed by the kernel.  The method “device_get_softc” is forward declared in /sys/sys/bus.h, and implemented in /sys/kern/subr_bus.c struct bcm_gpio_softc *sc = device_get_softc(dev); This method initializes a mutex for the driver: mtx_init(&sc->sc_mtx, “bcm gpio”, “gpio”, MTX_DEF); These macros #define BCM_GPIO_LOCK(_sc) mtx_lock(&_sc->sc_mtx) #define BCM_GPIO_UNLOCK(_sc) mtx_unlock(&_sc->sc_mtx) #define BCM_GPIO_LOCK_ASSERT(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED) Are used to serialize access in certain methods in the driver. These lines allocate memory and IRQs for the driver, as specified in the FTD: rid = 0; sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_mem_res) { device_printf(dev, “cannot allocate memory window\n”); return (ENXIO); } sc->sc_bst = rman_get_bustag(sc->sc_mem_res); sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res); rid = 0;   sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (!sc->sc_irq_res) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); device_printf(dev, “cannot allocate interrupt\n”); return (ENXIO); } These lines query the Open Firmware and verify the that device is indeed a GPIO controller, according to the FTD: /* Find our node. */ gpio = ofw_bus_get_node(sc->sc_dev); if (!OF_hasprop(gpio, “gpio-controller”)) /* Node is not a GPIO controller. */ goto fail; These lines set up the initial state of the GPIO controller pins: if (bcm_gpio_get_reserved_pins(sc) == -1) goto fail; /* Initialize the software controlled pins. */ for (i = 0, j = 0; j < BCM_GPIO_PINS – 1; j++) { if (bcm_gpio_pin_is_ro(sc, j)) continue; snprintf(sc->sc_gpio_pins[i].gp_name, GPIOMAXNAME, “pin %d”, j); func = bcm_gpio_get_function(sc, j); sc->sc_gpio_pins[i].gp_pin = j; sc->sc_gpio_pins[i].gp_caps = BCM_GPIO_DEFAULT_CAPS; sc->sc_gpio_pins[i].gp_flags = bcm_gpio_func_flag(func); i++; } sc->sc_gpio_npins = i; bcm_gpio_sysctl_init(sc); Finally, these lines add two devices “gpioc” and “gpiocbus”, and then attach the dev to the main bus device_add_child(dev, “gpioc”, device_get_unit(dev)); device_add_child(dev, “gpiobus”, device_get_unit(dev)); return (bus_generic_attach(dev)); The function “device_add_child” is implemented in /sys/kern/subr_bus.c.  The devices “gpio” and “gpioc” are generic devices, which work for all architectures, and they are in: /sys/dev/gpio/gpioc.c and /sys/dev/gpio/gpiobus.c.  The reason the kernel is able to find them by their device class names “gpio” and “gpiobus” is because they are registered with the kernel via these lines of code in gpioc.c and gpiobus.c. driver_t gpioc_driver = { “gpioc”, gpioc_methods, sizeof(struct gpioc_softc) }; devclass_t gpioc_devclass; DRIVER_MODULE(gpioc, gpio, gpioc_driver, gpioc_devclass, 0, 0); MODULE_VERSION(gpioc, 1); driver_t gpiobus_driver = { “gpiobus”, gpiobus_methods, sizeof(struct gpiobus_softc) }; devclass_t gpiobus_devclass; DRIVER_MODULE(gpiobus, gpio, gpiobus_driver, gpiobus_devclass, 0, 0); MODULE_VERSION(gpiobus, 1); The gpio driver which will finally appear on the Pi as “/dev/gpio0”, includes the header system header “/sys/gpio.h” and implements all its methods via that API, which ultimately comes back to /sys/arm/broadcom/bcm2835/bcm2835_gpio.c.  

Spelunking the FreeBSD ARM FDT

The FDT is the “Flattened Device Tree”, a simple device tree used in embedded FreeBSD, for example on a Pi.  Every architecture has a DTS file, which contains a human-readable list of hardware for that architecture.   For example, the Pi FTD is here.  You should notice that there are declarations for: cpu axi (the system bus) time & watchdog gpio: GPIO dma: DMA controller vc_mbox uart; serial controller sdhci; SD card controller memory display leds; the GPIO leds power regulators In other words, all the hardware in the Pi.  If you look at this datasheet from Broadcom, you should see that the hardware declared in the FDT file is the hardware on the Pi’s SOC chip, the Broadcom BCM2835. In my case, I’m specifically interested in RS-232 using the GPIO pins 14 and 15.  The FDT file contains this: /* UART0 */ pins_uart0_a: uart0_a { broadcom,pins = <14>, <15>; broadcom,function = “ALT0”; }; Which specifies that uart0 binds GPIO pins 14 and 15.   If you look in the datasheet, on page 101, you’ll see that GPIO pins 14 and 15, in ALT0 mode are serial TX and RX respectively. If you have FreeBSD up on a Pi, and you have the /dev/openfirm device compiled in to your kernel, you can find uart0 using /usr/sbin/ofwdump. The output “ofwdump -a” on my Pi shows: root@raspberry-pi:/etc # ofwdump -a Node 0x48: Node 0xf8: system Node 0x150: cpus Node 0x15c: cpu@0 Node 0x18c: axi Node 0x1f8: interrupt-controller Node 0x29c: timer Node 0x344: armtimer Node 0x3bc: watchdog0 Node 0x41c: gpio Node 0x538: bsc0_a Node 0x570: bsc0_b Node 0x5a8: bsc0_c Node 0x5e0: bsc1_a Node 0x618: bsc1_b Node 0x650: gpclk0_a Node 0x688: gpclk0_b Node 0x6c0: gpclk0_c Node 0x6f8: gpclk0_d Node 0x730: gpclk1_a Node 0x768: gpclk1_b Node 0x7a0: gpclk1_c Node 0x7d8: gpclk1_d Node 0x810: gpclk2_a Node 0x848: gpclk2_b Node 0x880: spi0_a Node 0x8c4: spi0_b Node 0x908: pwm0_a Node 0x93c: pwm0_b Node 0x970: pwm0_c Node 0x9a4: pwm1_a Node 0x9d8: pwm1_b Node 0xa0c: pwm1_c Node 0xa40: pwm1_d Node 0xa74: uart0_a Node 0xaac: uart0_b Node 0xae4: uart0_c Node 0xb1c: uart0_fc_a Node 0xb58: uart0_fc_b Node 0xb94: uart0_fc_c Node 0xbd0: pcm_a Node 0xc10: pcm_b Node 0xc50: sm_addr_a Node 0xc9c: sm_addr_b Node 0xce8: sm_ctl_a Node 0xd24: sm_ctl_b Node 0xd60: sm_data_8bit_a Node 0xdb8: sm_data_8bit_b Node 0xe10: sm_data_16bit Node 0xe68: sm_data_18bit Node 0xea8: bscsl Node 0xee0: spisl Node 0xf20: spi1 Node 0xf68: uart1_a Node 0xfa0: uart1_b Node 0xfd8: uart1_c Node 0x1010: uart1_fc_a Node 0x104c: uart1_fc_b Node 0x1088: uart1_fc_c Node 0x10c4: spi2 Node 0x110c: arm_jtag_trst Node 0x1148: arm_jtag_a Node 0x1190: arm_jtag_b Node 0x11d8: reserved Node 0x1238: dma Node 0x12f8: mbox Node 0x1384: sdhci Node 0x1414: uart0 Node 0x14c8: vchiq Node 0x1530: usb Node 0x15e0: hub Node 0x1638: ethernet Node 0x16a0: memory Node 0x16d8: display Node 0x176c: leds Node 0x1790: ok Node 0x17f4: regulator Node 0x18ec: regulator@0 Node 0x1998: regulator@3 Node 0x1a48: aliases Node 0x1a70: chosen The list of devices on my Pi matches the list in the FTD (here). I can also use devinfo to find the devices that are actuallly found in my system.  Here’s the output, with the USB stuff removed: root@raspberry-pi:/etc # devinfo -r nexus0 fdtbus0 simplebus0 intc0 I/O memory: 0x2000b200-0x2000b3ff systimer0 Interrupt request lines: 8 9 10 11 I/O memory: 0x20003000-0x20003fff bcmwd0 I/O memory: 0x2010001c-0x20100027 gpio0 Interrupt request lines: 57 I/O memory: 0x20200000-0x202000af gpioc0 gpiobus0 bcm_dma0 Interrupt request lines: 24 25 26 27 28 29 30 31 32 33 34 35 I/O memory: 0x20007000-0x20007fff mbox0 Interrupt request lines: 1 I/O memory: 0x2000b880-0x2000b8bf sdhci_bcm0 Interrupt request lines: 70 I/O memory: 0x20300000-0x203000ff mmc0 mmcsd0 uart0 Interrupt request lines: 65 I/O memory: 0x20201000-0x20201fff dwcotg0 Interrupt request lines: 17 I/O memory: 0x20980000-0x2099ffff usbus0 uhub0 uhub1 smsc0 miibus0 ukphy0 ukbd0 fb0 simplebus1 If I check the dmesg output I see that I do indeed have a uart0 that FreeBSD found on boot: root@raspberry-pi:/dev # dmesg | grep uart uart0: <PrimeCell UART (PL011)> mem 0x20201000-0x20201fff irq 65 on simplebus0 Verifying with ofwdump: root@raspberry-pi:/etc # ofwdump -p uart0 Node 0x1414: uart0 compatible: 62 72 6f 61 64 63 6f 6d 2c 62 63 6d 32 38 33 35 2d 75 61 72 74 00 62 72 6f 61 64 63 6f 6d 2c 62 63 6d 32 37 30 38 2d 75 61 72 74 00 61 72 6d 2c 70 6c 30 31 31 00 61 72 6d 2c 70 72 69 6d 65 63 65 6c 6c 00 reg: 00 20 10 00 00 00 10 00 interrupts: 00 00 00 41 interrupt-parent: 00 00 00 01 clock-frequency: 00 2d c6 c0 reg-shift: 00 00 00 02 You can see that the data ofwdump returns for uart0 matches with the FTD and with uart0 that FreeBSD reported.  The IRQ that ofwdump returns is 0x41, which is 65 in base-10; exactly what’s expected. If you look at the bottom of the FTD file, you will see that uart0 is bound to stdin and stdout: chosen { bootargs = “”; /* Set by VideoCore */ stdin = “uart0”; stdout = “uart0”; }; There is a description of the “/chosen” node in the FDT here and here from devicetree.org. For more information, there is this excellent presentation, this FreeBSD Wiki page, and this white paper.   There is a wiki here, with quite a lot of good information on Flattened Device Tree. Looking at the /dev/ tree I see: crw——- 1 root wheel 0x1f Apr 27 13:42 ttyu0 crw——- 1 root wheel 0x20 Apr 27 21:42 ttyu0.init crw——- 1 root wheel 0x21 Apr 27 21:42 ttyu0.lock crw-rw—- 1 uucp dialer 0x22 Apr 27 21:42 cuau0 crw-rw—- 1 uucp dialer 0x23 Apr 27 21:42 cuau0.init crw-rw—- 1 uucp dialer 0x24 Apr 27 21:42 cuau0.lock I these devices are the sio device, bound to uart0. /dev/ttyu0 was declared in the /etc/ttys.  The build script here was used to create my Pi install, and the specific line that declares this tty was; ttyu0 “/usr/libexec/getty 3wire.115200” dialup on secure If you’re interested in what “3wire.115200” actually means, it’s declared in /etc/gettytab.  The details of setting up tty devices in FreeBSD are here, in the FreeBSD Handbook.  

Simple pf for Raspberry Pi

One of the best things about the Pi is that it’s small, and portable.  It’s very tempting to use it on wireless networks, unattended.  Sadly, in the real world, any wireless network can be hacked, and any device on wireless should be considered vulnerable.  So, pf is an easy way to harden your Pi. The first thing you’ll need is a FreeBSD Pi install, which has the pf device in the kernel.  You can get that here, or just build yourself a kernel with pf. Then, you will need some simple rules.  I used these rules in the file “/etc/pf.conf” # options set block-policy return set optimization conservative # normalization scrub in all scrub out all # default, deny everything block in log all pass out quick #icmp pass out inet proto icmp from any to any keep state pass in quick inet proto icmp from any to any keep state #ssh pass out inet proto tcp from any to any port 22 keep state pass in quick inet proto tcp from any to any port 22 keep state #localhost pass in quick on lo0 all pass out quick on lo0 all Simply; I block everything other than ssh, icmp (ping). In order to load the rules: pfctl -f /etc/pf.conf and to show the current status of the packet filter: pfctl -s all You’ll want FreeBSD to load your pf rules when it starts, so add the below to “/etc/rc.conf” pf_enable=”YES”

Identifying unknown hardware a Pi running FreeBSD

I’ve been building FreeBSD-CURRENT images for my Pi, and I’ve noticed I have this: ugen0.5: <vendor 0x7392> at usbus0 So, it turns out I have one of these plugged in, and it seems likely that the unknown USB device is ugen0.5.  So firstly, there is the question of “ugen”.  You can read about it here.  Essentially when FreeBSD finds a USB device it doesn’t have a specific driver for, it assigns it the ugen driver.   In my case, the driver is attached to device 0, endpoint 5. To get more info, I can use usbconfig. root@raspberry-pi:/home/tom # usbconfig -d ugen0.5 dump_info ugen0.5: <product 0x7811 vendor 0x7392> at usbus0, cfg=0 md=HOST spd=HIGH (480Mbps) pwr=ON (500mA) The product code noted in by usbconfig, is 0x7811, which matches with the Edimax 7811. So to find out what “vendor 0x7392” is, I used http://usb-ids.gowdy.us/index.html. If you search in this file, you’ll find this: 7392 Edimax Technology Co., Ltd 7711 EW-7711UTn nLite Wireless Adapter [Ralink RT2870] 7717 EW-7717UN 802.11n Wireless Adapter [Ralink RT2870] 7718 EW-7718UN 802.11n Wireless Adapter [Ralink RT2870] 7722 EW-7722UTn 802.11n Wireless Adapter [Ralink RT307x] 7811 EW-7811Un 802.11n Wireless Adapter [Realtek RTL8188CUS] So, my device “ugen0.5” is the Edimax EW-7811Un.  It uses a Realtek  RTL8188CUS chipset.   Some Edimax devices are supported by the rum driver, but that driver appears to support the Ralink chipset, rather than Realtek.  If FreeBSD did support this chipset, it might be more likely to be supported by the urtw driver.  The man page doesn’t mention the 8188CUS Realtek chipset, however. In fact, you can look at the source for the WLAN USB drivers in FreeBSD here and you will find the source for the urtw driver here.  It supports the RTL 8187B. If you take a look in /sys/dev/usb/usbdevs, you will see a list of the USB devices FreeBSD knows about.  There are some Edimax devices, and the 7811 is listed: /* Edimax products */ 1556 product EDIMAX EW7318USG 0x7318 USB Wireless dongle 1557 product EDIMAX RT2870_1 0x7711 RT2870 1558 product EDIMAX EW7717 0x7717 EW-7717 1559 product EDIMAX EW7718 0x7718 EW-7718 1560 product EDIMAX EW7811UN 0x7811 EW-7811Un So, it appears that FreeBSD knows that it’s a ED7811UN, but RTL8188 chipset is not supported.

Creating a custom FreeBSD build for Raspberry Pi

The default FreeBSD kernel for Raspberry Pi can be found here in the FreeBSD source browser.  It’s everything you need to boot FreeBSD on a Pi, and includes GPIO support.  However, the Pi is a nice small computer, so it’s likely that you’ll want it to include options such as Wifi, and pf.  In my case, I wanted IPSEC and support for USB Serial via uftdi. In order to add drivers to the kernel, it’s useful to have a list of the drivers that can be added.  That exists here.  The man pages, are a useful place to look up what a specific driver does, what hardware it supports, etc. I added this stuff. # WLAN required for wireless device wlan device wlan_wep device wlan_ccmp device wlan_tkip # USB wireless support device ehci device uhci device ohci device wlan_amrr device firmware device urtw device zyd device ural device upgt device uath device run device rum # USB ethernet support device smcphy device mii device smsc # USB serial # device uftdi #proc filesystem options PROCFS #Process filesystem (requires PSEUDOFS) options PSEUDOFS #Pseudo-filesystem framework #pf device pf # IPsec interface. device enc I also renamed my kernel configuration ident RPI-B-WIFI The kernel options I added give me: WLAN support All the USB Wifi drivers pf, the packet filter the /proc filesystem the enc device, for IPSEC uftdi I copied this kernel configuration (name RPI-B-WIFI) to the right spot on my local source tree cp RPI-B-WIFI src/FreeBSD/head/sys/arm/conf/ Then I used the freebsd-arm-tools to create a 4GB disk image, with this kernel, a small swap, and the full ports tree: sh build-arm-image.sh -s4 -u -p -mtom@khubla.com -w512 -kRPI-B-WIFI

A simple FreeBSD GPIO Example

I’ve been making FreeBSD-Current builds for my Raspberry Pi.  Naturally I wanted to try out the GPIO support to find out just how simple it is to use.   Here’s an example, and the full code is here. int main (int argc, char *argv[]) { char *device = “/dev/gpioc0”; int gpiofd = open(device, O_RDWR); if (-1!=gpiofd) { // get total pins int maxpin=0; if (ioctl(gpiofd, GPIOMAXPIN, &maxpin) < 0) { perror(“ioctl(GPIOMAXPIN)”); exit(1); } else { printf(“GPIO has %d pins\n”,maxpin); // walk the pins printf(“pin# description capabilties flags\n”); for (int i = 0; i <= maxpin; i++) { struct gpio_pin pinStatus; pinStatus.gp_pin=i; int r = ioctl(gpiofd, GPIOGETCONFIG, &pinStatus); if ( r>= 0) { printf(“%i: %s 0x%x 0x%x\n”,i, pinStatus.gp_name, pinStatus.gp_caps, pinStatus.gp_flags); } else { printf(“failed to get status of pin %d with status %d\n”,i,r); } } } } else { printf(“failed to open device with error%d\n”,gpiofd); } }

freebsd-arm-tools

I’ve had the opportunity to make some updates to the freebsd-arm-tools.   If you’re interested in trying FreeBSD-current on a Raspberry Pi, I’ve posted a build at http://files.khubla.com/freebsd-raspberry-pi/ which has full Wifi and pf support.  If you’re interested, the kernel configuration is here. Installing the FreeBSD on the Pi is simple.  Firstly, get an image, such as this one http://files.khubla.com/freebsd-raspberry-pi/FreeBSD-HEAD-r249996-ARMv6-RPI-B-WIFI-3GB.img.tgz Extract the Image tar zxvf FreeBSD-HEAD-r249907-ARMv6-RPI-B-WIFI-4GB.img.tgz Unmount the SD card sudo diskutil unmount /dev/disk1s1 Burn the image to the SD card sudo dd if=FreeBSD-HEAD-r249907-ARMv6-RPI-B-WIFI-4GB.img of=/dev/rdisk1 bs=1m Unmount the SD again sudo diskutil unmount /dev/disk1s1 Then, boot the Pi from the SD card.   ssh to root is disabled, however you can log in as username “pi” with password “raspberry”, and su to root.