263 lines
7.6 KiB
Go
263 lines
7.6 KiB
Go
|
// Copyright 2017 The Periph Authors. All rights reserved.
|
||
|
// Use of this source code is governed under the Apache License, Version 2.0
|
||
|
// that can be found in the LICENSE file.
|
||
|
|
||
|
// Package spireg defines the SPI registry for SPI ports discovered on the host.
|
||
|
//
|
||
|
// SPI ports discovered on the host are automatically registered in the SPI
|
||
|
// registry by host.Init().
|
||
|
package spireg
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
|
||
|
"periph.io/x/periph/conn/spi"
|
||
|
)
|
||
|
|
||
|
// Opener opens an handle to a port.
|
||
|
//
|
||
|
// It is provided by the actual port driver.
|
||
|
type Opener func() (spi.PortCloser, error)
|
||
|
|
||
|
// Ref references a SPI port.
|
||
|
//
|
||
|
// It is returned by All() to enumerate all registered ports.
|
||
|
type Ref struct {
|
||
|
// Name of the port.
|
||
|
//
|
||
|
// It must not be a sole number. It must be unique across the host.
|
||
|
Name string
|
||
|
// Aliases are the alternative names that can be used to reference this port.
|
||
|
Aliases []string
|
||
|
// Number of the bus or -1 if the bus doesn't have any "native" number.
|
||
|
//
|
||
|
// Buses provided by the CPU normally have a 0 based number. Buses provided
|
||
|
// via an addon (like over USB) generally are not numbered.
|
||
|
//
|
||
|
// The port is a bus number plus a CS line.
|
||
|
Number int
|
||
|
// Open is the factory to open an handle to this SPI port.
|
||
|
Open Opener
|
||
|
}
|
||
|
|
||
|
// Open opens a SPI port by its name, an alias or its number and returns an
|
||
|
// handle to it.
|
||
|
//
|
||
|
// Specify the empty string "" to get the first available port. This is the
|
||
|
// recommended default value unless an application knows the exact port to use.
|
||
|
//
|
||
|
// Each port can register multiple aliases, each leading to the same port
|
||
|
// handle.
|
||
|
//
|
||
|
// "Bus number" is a generic concept that is highly dependent on the platform
|
||
|
// and OS. On some platform, the first port may have the number 0, 1 or as high
|
||
|
// as 32766. Bus numbers are not necessarily continuous and may not start at 0.
|
||
|
// It was observed that the bus number as reported by the OS may change across
|
||
|
// OS revisions.
|
||
|
//
|
||
|
// A SPI port is constructed of the bus number and the chip select (CS) number.
|
||
|
//
|
||
|
// When the SPI port is provided by an off board plug and play bus like USB via
|
||
|
// a FT232H USB device, there can be no associated number.
|
||
|
func Open(name string) (spi.PortCloser, error) {
|
||
|
var r *Ref
|
||
|
var err error
|
||
|
func() {
|
||
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
if len(byName) == 0 {
|
||
|
err = errors.New("spireg: no port found; did you forget to call Init()?")
|
||
|
return
|
||
|
}
|
||
|
if len(name) == 0 {
|
||
|
r = getDefault()
|
||
|
return
|
||
|
}
|
||
|
// Try by name, by alias, by number.
|
||
|
if r = byName[name]; r == nil {
|
||
|
if r = byAlias[name]; r == nil {
|
||
|
if i, err2 := strconv.Atoi(name); err2 == nil {
|
||
|
r = byNumber[i]
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if r == nil {
|
||
|
return nil, errors.New("spireg: can't open unknown port: " + strconv.Quote(name))
|
||
|
}
|
||
|
return r.Open()
|
||
|
}
|
||
|
|
||
|
// All returns a copy of all the registered references to all know SPI ports
|
||
|
// available on this host.
|
||
|
//
|
||
|
// The list is sorted by the port name.
|
||
|
func All() []*Ref {
|
||
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
out := make([]*Ref, 0, len(byName))
|
||
|
for _, v := range byName {
|
||
|
r := &Ref{Name: v.Name, Aliases: make([]string, len(v.Aliases)), Number: v.Number, Open: v.Open}
|
||
|
copy(r.Aliases, v.Aliases)
|
||
|
out = insertRef(out, r)
|
||
|
}
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
// Register registers a SPI port.
|
||
|
//
|
||
|
// Registering the same port name twice is an error, e.g. o.Name(). o.Number()
|
||
|
// can be -1 to signify that the port doesn't have an inherent "bus number". A
|
||
|
// good example is a port provided over a FT232H device connected on an USB bus.
|
||
|
// In this case, the port name should be created from the serial number of the
|
||
|
// device for unique identification.
|
||
|
//
|
||
|
// Only ports with the CS #0 are registered with their number.
|
||
|
func Register(name string, aliases []string, number int, o Opener) error {
|
||
|
if len(name) == 0 {
|
||
|
return errors.New("spireg: can't register a port with no name")
|
||
|
}
|
||
|
if o == nil {
|
||
|
return errors.New("spireg: can't register port " + strconv.Quote(name) + " with nil Opener")
|
||
|
}
|
||
|
if number < -1 {
|
||
|
return errors.New("spireg: can't register port " + strconv.Quote(name) + " with invalid port number " + strconv.Itoa(number))
|
||
|
}
|
||
|
if _, err := strconv.Atoi(name); err == nil {
|
||
|
return errors.New("spireg: can't register port " + strconv.Quote(name) + " with name being only a number")
|
||
|
}
|
||
|
if strings.Contains(name, ":") {
|
||
|
return errors.New("spireg: can't register port " + strconv.Quote(name) + " with name containing ':'")
|
||
|
}
|
||
|
for _, alias := range aliases {
|
||
|
if len(alias) == 0 {
|
||
|
return errors.New("spireg: can't register port " + strconv.Quote(name) + " with an empty alias")
|
||
|
}
|
||
|
if name == alias {
|
||
|
return errors.New("spireg: can't register port " + strconv.Quote(name) + " with an alias the same as the port name")
|
||
|
}
|
||
|
if _, err := strconv.Atoi(alias); err == nil {
|
||
|
return errors.New("spireg: can't register port " + strconv.Quote(name) + " with an alias that is a number: " + strconv.Quote(alias))
|
||
|
}
|
||
|
if strings.Contains(alias, ":") {
|
||
|
return errors.New("spireg: can't register port " + strconv.Quote(name) + " with an alias containing ':': " + strconv.Quote(alias))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
if _, ok := byName[name]; ok {
|
||
|
return errors.New("spireg: can't register port " + strconv.Quote(name) + " twice")
|
||
|
}
|
||
|
if _, ok := byAlias[name]; ok {
|
||
|
return errors.New("spireg: can't register port " + strconv.Quote(name) + " twice; it is already an alias")
|
||
|
}
|
||
|
if number != -1 {
|
||
|
if _, ok := byNumber[number]; ok {
|
||
|
return errors.New("spireg: can't register port " + strconv.Quote(name) + "; port number " + strconv.Itoa(number) + " is already registered")
|
||
|
}
|
||
|
}
|
||
|
for _, alias := range aliases {
|
||
|
if _, ok := byName[alias]; ok {
|
||
|
return errors.New("spireg: can't register port " + strconv.Quote(name) + " twice; alias " + strconv.Quote(alias) + " is already a port")
|
||
|
}
|
||
|
if _, ok := byAlias[alias]; ok {
|
||
|
return errors.New("spireg: can't register port " + strconv.Quote(name) + " twice; alias " + strconv.Quote(alias) + " is already an alias")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
r := &Ref{Name: name, Aliases: make([]string, len(aliases)), Number: number, Open: o}
|
||
|
copy(r.Aliases, aliases)
|
||
|
byName[name] = r
|
||
|
if number != -1 {
|
||
|
byNumber[number] = r
|
||
|
}
|
||
|
for _, alias := range aliases {
|
||
|
byAlias[alias] = r
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Unregister removes a previously registered SPI port.
|
||
|
//
|
||
|
// This can happen when a SPI port is exposed via an USB device and the device
|
||
|
// is unplugged.
|
||
|
func Unregister(name string) error {
|
||
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
r := byName[name]
|
||
|
if r == nil {
|
||
|
return errors.New("spireg: can't unregister unknown port name " + strconv.Quote(name))
|
||
|
}
|
||
|
delete(byName, name)
|
||
|
delete(byNumber, r.Number)
|
||
|
for _, alias := range r.Aliases {
|
||
|
delete(byAlias, alias)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
//
|
||
|
|
||
|
var (
|
||
|
mu sync.Mutex
|
||
|
byName = map[string]*Ref{}
|
||
|
// Caches
|
||
|
byNumber = map[int]*Ref{}
|
||
|
byAlias = map[string]*Ref{}
|
||
|
)
|
||
|
|
||
|
// getDefault returns the Ref that should be used as the default port.
|
||
|
func getDefault() *Ref {
|
||
|
var o *Ref
|
||
|
if len(byNumber) == 0 {
|
||
|
// Fallback to use byName using a lexical sort.
|
||
|
name := ""
|
||
|
for n, o2 := range byName {
|
||
|
if len(name) == 0 || n < name {
|
||
|
o = o2
|
||
|
name = n
|
||
|
}
|
||
|
}
|
||
|
return o
|
||
|
}
|
||
|
number := int((^uint(0)) >> 1)
|
||
|
for n, o2 := range byNumber {
|
||
|
if number > n {
|
||
|
number = n
|
||
|
o = o2
|
||
|
}
|
||
|
}
|
||
|
return o
|
||
|
}
|
||
|
|
||
|
func insertRef(l []*Ref, r *Ref) []*Ref {
|
||
|
n := r.Name
|
||
|
i := search(len(l), func(i int) bool { return l[i].Name > n })
|
||
|
l = append(l, nil)
|
||
|
copy(l[i+1:], l[i:])
|
||
|
l[i] = r
|
||
|
return l
|
||
|
}
|
||
|
|
||
|
// search implements the same algorithm as sort.Search().
|
||
|
//
|
||
|
// It was extracted to to not depend on sort, which depends on reflect.
|
||
|
func search(n int, f func(int) bool) int {
|
||
|
lo := 0
|
||
|
for hi := n; lo < hi; {
|
||
|
if i := int(uint(lo+hi) >> 1); !f(i) {
|
||
|
lo = i + 1
|
||
|
} else {
|
||
|
hi = i
|
||
|
}
|
||
|
}
|
||
|
return lo
|
||
|
}
|