github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/gpio/gpio_linux.go (about)

     1  // Copyright 2019 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package gpio provides functions for interacting with GPIO pins via the
     6  // GPIO Sysfs Interface for Userspace.
     7  package gpio
     8  
     9  import (
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  )
    16  
    17  const gpioPath = "/sys/class/gpio"
    18  
    19  // Value represents the value of a gpio pin
    20  type Value bool
    21  
    22  // Gpio pin values can either be low (0) or high (1)
    23  const (
    24  	Low  Value = false
    25  	High Value = true
    26  )
    27  
    28  // Dir returns the representation that sysfs likes to use.
    29  func (v Value) Dir() string {
    30  	if v == Low {
    31  		return "low"
    32  	}
    33  	return "high"
    34  }
    35  
    36  func (v Value) String() string {
    37  	if v == Low {
    38  		return "0"
    39  	}
    40  	return "1"
    41  }
    42  
    43  func readInt(filename string) (int, error) {
    44  	// Get base offset (the first GPIO managed by this chip)
    45  	buf, err := os.ReadFile(filename)
    46  	if err != nil {
    47  		return 0, fmt.Errorf("failed to read integer out of %s: %v", filename, err)
    48  	}
    49  	baseStr := strings.TrimSpace(string(buf))
    50  	num, err := strconv.Atoi(baseStr)
    51  	if err != nil {
    52  		return 0, fmt.Errorf("could not convert %s contents %s to integer: %v", filename, baseStr, err)
    53  	}
    54  	return num, nil
    55  }
    56  
    57  // GetPinID computes the sysfs pin ID for a specific port on a specific GPIO
    58  // controller chip. The controller arg is matched to a gpiochip's label in
    59  // sysfs. GetPinID gets the base offset of that chip, and adds the specific
    60  // pin number.
    61  func GetPinID(controller string, pin uint) (int, error) {
    62  	controllers, err := filepath.Glob(fmt.Sprintf("%s/gpiochip*", gpioPath))
    63  	if err != nil {
    64  		return 0, err
    65  	}
    66  
    67  	for _, c := range controllers {
    68  		// Get label (name of the controller)
    69  		buf, err := os.ReadFile(filepath.Join(c, "label"))
    70  		if err != nil {
    71  			return 0, fmt.Errorf("failed to read label of %s: %v", c, err)
    72  		}
    73  		label := strings.TrimSpace(string(buf))
    74  
    75  		// Check that this is the controller we want
    76  		if strings.TrimSpace(label) != controller {
    77  			continue
    78  		}
    79  
    80  		// Get base offset (the first GPIO managed by this chip)
    81  		base, err := readInt(filepath.Join(c, "base"))
    82  		if err != nil {
    83  			return 0, fmt.Errorf("failed to read base: %v", err)
    84  		}
    85  
    86  		// Get the number of GPIOs managed by this chip.
    87  		ngpio, err := readInt(filepath.Join(c, "ngpio"))
    88  		if err != nil {
    89  			return 0, fmt.Errorf("failed to read number of gpios: %v", err)
    90  		}
    91  		if int(pin) >= ngpio {
    92  			return 0, fmt.Errorf("requested pin %d of controller %s, but controller only has %d pins", pin, controller, ngpio)
    93  		}
    94  
    95  		return base + int(pin), nil
    96  	}
    97  
    98  	return 0, fmt.Errorf("could not find controller %s", controller)
    99  }
   100  
   101  // SetOutputValue configures the gpio as an output pin with the given value.
   102  func SetOutputValue(pin int, val Value) error {
   103  	dir := val.Dir()
   104  	path := filepath.Join(gpioPath, fmt.Sprintf("gpio%d", pin), "direction")
   105  	outFile, err := os.OpenFile(path, os.O_WRONLY, 0)
   106  	if err != nil {
   107  		return fmt.Errorf("failed to open %s: %v", path, err)
   108  	}
   109  	defer outFile.Close()
   110  	if _, err := outFile.WriteString(dir); err != nil {
   111  		return fmt.Errorf("failed to set gpio %d to %s: %v", pin, dir, err)
   112  	}
   113  	return nil
   114  }
   115  
   116  // ReadValue returns the value of the given gpio pin. If the read was
   117  // unsuccessful, it returns a value of Low and the associated error.
   118  func ReadValue(pin int) (Value, error) {
   119  	path := filepath.Join(gpioPath, fmt.Sprintf("gpio%d", pin), "value")
   120  	buf, err := os.ReadFile(path)
   121  	if err != nil {
   122  		return Low, fmt.Errorf("failed to read value of gpio %d: %v", pin, err)
   123  	}
   124  	switch string(buf) {
   125  	case "0\n":
   126  		return Low, nil
   127  	case "1\n":
   128  		return High, nil
   129  	}
   130  	return Low, fmt.Errorf("invalid value of gpio %d: %s", pin, string(buf))
   131  }
   132  
   133  // Export enables access to the given gpio pin.
   134  func Export(pin int) error {
   135  	path := filepath.Join(gpioPath, "export")
   136  	outFile, err := os.OpenFile(path, os.O_WRONLY, 0)
   137  	if err != nil {
   138  		return fmt.Errorf("failed to open %s: %v", path, err)
   139  	}
   140  	defer outFile.Close()
   141  	if _, err := outFile.WriteString(strconv.Itoa(pin)); err != nil {
   142  		return fmt.Errorf("failed to export gpio %d: %v", pin, err)
   143  	}
   144  	return nil
   145  }