diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e0671ba --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +rc-pca9685 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50bc561 --- /dev/null +++ b/.gitignore @@ -0,0 +1,284 @@ +# Created by .ignore support plugin (hsz.mobi) +### Emacs template +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +# network security +/network-security.data + + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Vim template +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +### macOS template +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Xcode template +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Gcc Patch +/*.gcno + +### Go template +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +### Eclipse template +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +### VisualStudioCode template +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e62635c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM --platform=$BUILDPLATFORM golang:alpine AS builder + +ARG TARGETPLATFORM +ARG BUILDPLATFORM + +WORKDIR /go/src +ADD . . + +RUN GOOS=$(echo $TARGETPLATFORM | cut -f1 -d/) && \ + GOARCH=$(echo $TARGETPLATFORM | cut -f2 -d/) && \ + GOARM=$(echo $TARGETPLATFORM | cut -f3 -d/ | sed "s/v//" ) && \ + CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} GOARM=${GOARM} go build -mod vendor -tags netgo ./cmd/rc-pca9685/ + + +FROM gcr.io/distroless/static + +USER 1234 +COPY --from=builder /go/src/rc-pca9685 /go/bin/rc-pca9685 +ENTRYPOINT ["/go/bin/rc-pca9685"]] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5a58c8d --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# robocar-pca9685 + +Microservice part for PCA9685 controller diff --git a/actuator/pca9685.go b/actuator/pca9685.go new file mode 100644 index 0000000..10d4986 --- /dev/null +++ b/actuator/pca9685.go @@ -0,0 +1,39 @@ +package actuator + +import ( + "log" + "periph.io/x/periph/conn/i2c/i2creg" + "periph.io/x/periph/conn/physic" + "periph.io/x/periph/experimental/devices/pca9685" + "periph.io/x/periph/host" +) + +var ( + device *pca9685.Dev +) + +func init() { + log.Print("init pca9685 controller") + _, err := host.Init() + if err != nil { + log.Fatalf("unable to init host: %v", err) + } + + log.Print("open i2c bus") + bus, err := i2creg.Open("") + if err != nil { + log.Fatalf("unable to init i2c bus: %v", err) + } + log.Print("i2c bus opened") + + device, err = pca9685.NewI2C(bus, pca9685.I2CAddr) + if err != nil { + log.Fatalf("unable to init pca9685 bus: %v", err) + } + log.Printf("set pwm frequency to %d", 60) + err = device.SetPwmFreq(60 * physic.Hertz) + if err != nil { + log.Fatalf("unable to set pwm frequency: %v", err) + } + log.Print("init done") +} diff --git a/actuator/steering.go b/actuator/steering.go new file mode 100644 index 0000000..34c260c --- /dev/null +++ b/actuator/steering.go @@ -0,0 +1,44 @@ +package actuator + +import ( + "github.com/cyrilix/robocar-pca9685/util" + "log" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/experimental/devices/pca9685" +) + +const ( + LeftAngle = -1. + RightAngle = 1. +) + +type Steering struct { + channel int + leftPWM, rightPWM int + dev *pca9685.Dev +} + +func (s *Steering) SetPulse(pulse int) { + err := s.dev.SetPwm(s.channel, 0, gpio.Duty(pulse)) + if err != nil { + log.Printf("unable to set throttle pwm value: %v", err) + } + +} + +// Set percent value steering +func (s *Steering) SetPercentValue(p float64) { + // map absolute angle to angle that vehicle can implement. + pulse := util.MapRange(p, LeftAngle, RightAngle, float64(s.leftPWM), float64(s.rightPWM)) + s.SetPulse(pulse) +} + +func NewSteering(channel, leftPWM, rightPWM int) *Steering { + t := Steering{ + channel: channel, + dev: device, + leftPWM: leftPWM, + rightPWM: rightPWM, + } + return &t +} diff --git a/actuator/throttle.go b/actuator/throttle.go new file mode 100644 index 0000000..149a3ed --- /dev/null +++ b/actuator/throttle.go @@ -0,0 +1,53 @@ +package actuator + +import ( + "github.com/cyrilix/robocar-pca9685/util" + "log" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/experimental/devices/pca9685" +) + +const ( + MinThrottle = -1 + MaxThrottle = 1 +) + +type Throttle struct { + channel int + zeroPulse, minPulse, maxPulse int + dev *pca9685.Dev +} + +func (t *Throttle) SetPulse(pulse int) { + err := t.dev.SetPwm(t.channel, 0, gpio.Duty(pulse)) + if err != nil { + log.Printf("unable to set throttle pwm value: %v", err) + } + +} + +// Set percent value throttle +func (t *Throttle) SetPercentValue(p float64) { + var pulse int + if p > 0 { + pulse = util.MapRange(p, 0, MaxThrottle, float64(t.zeroPulse), float64(t.maxPulse)) + } else { + pulse = util.MapRange(p, MinThrottle, 0, float64(t.minPulse), float64(t.zeroPulse)) + } + t.SetPulse(pulse) +} + +func NewThrottle(channel, zeroPulse, minPulse, maxPulse int) *Throttle { + t := Throttle{ + channel: channel, + dev: device, + zeroPulse: zeroPulse, + minPulse: minPulse, + maxPulse: maxPulse, + } + + log.Printf("send zero pulse to calibrate ESC: %v", zeroPulse) + t.SetPulse(zeroPulse) + + return &t +} diff --git a/cmd/rc-pca9685/rc-pca9685.go b/cmd/rc-pca9685/rc-pca9685.go new file mode 100644 index 0000000..63a990b --- /dev/null +++ b/cmd/rc-pca9685/rc-pca9685.go @@ -0,0 +1,94 @@ +package main + +import ( + "flag" + "github.com/cyrilix/robocar-base/cli" + rc "github.com/cyrilix/robocar-pca9685/actuator" + "github.com/cyrilix/robocar-pca9685/part" + "log" + "os" +) + +const ( + DefaultClientId = "robocar-pca9685" + SteeringChannel = 0 + ThrottleChannel = 1 + + ThrottleStoppedPWM = 1455 + ThrottleMinPWM = 1113 + ThrottleMaxPWM = 1800 + + SteeringLeftPWM = 1004 + SteeringRightPWM = 1986 +) + +func main() { + var mqttBroker, username, password, clientId, topicThrottle, topicSteering string + + mqttQos := cli.InitIntFlag("MQTT_QOS", 0) + _, mqttRetain := os.LookupEnv("MQTT_RETAIN") + + cli.InitMqttFlags(DefaultClientId, &mqttBroker, &username, &password, &clientId, &mqttQos, &mqttRetain) + + var throttleChannel, throttleStoppedPWM, throttleMinPWM, throttleMaxPWM int + if err := cli.SetIntDefaultValueFromEnv(&throttleChannel, "THROTTLE_CHANNEL", ThrottleChannel); err != nil { + log.Printf("unable to init throttleChannel arg: %v", err) + } + if err := cli.SetIntDefaultValueFromEnv(&throttleStoppedPWM, "THROTTLE_STOPPED_PWM", ThrottleStoppedPWM); err != nil { + log.Printf("unable to init throttleStoppedPWM arg: %v", err) + } + if err := cli.SetIntDefaultValueFromEnv(&throttleMinPWM, "THROTTLE_MIN_PWM", ThrottleMinPWM); err != nil { + log.Printf("unable to init throttleMinPWM arg: %v", err) + } + if err := cli.SetIntDefaultValueFromEnv(&throttleMaxPWM, "THROTTLE_MAX_PWM", ThrottleMaxPWM); err != nil { + log.Printf("unable to init throttleMaxPWM arg: %v", err) + } + + var steeringChannel, steeringLeftPWM, steeringRightPWM int + if err := cli.SetIntDefaultValueFromEnv(&steeringChannel, "STEERING_CHANNEL", SteeringChannel); err != nil { + log.Printf("unable to init steeringChannel arg: %v", err) + } + if err := cli.SetIntDefaultValueFromEnv(&steeringLeftPWM, "STEERING_LEFT_PWM", SteeringLeftPWM); err != nil { + log.Printf("unable to init steeringLeftPWM arg: %v", err) + } + if err := cli.SetIntDefaultValueFromEnv(&steeringRightPWM, "STEERING_RIGHT_PWM", SteeringRightPWM); err != nil { + log.Printf("unable to init steeringRightPWM arg: %v", err) + } + + var updatePWMFrequency int + if err := cli.SetIntDefaultValueFromEnv(&updatePWMFrequency, "UPDATE_PWM_FREQUENCY", 25); err != nil { + log.Printf("unable to init updatePWMFrequency arg: %v", err) + } + + flag.StringVar(&topicThrottle, "mqtt-topic-throttle", os.Getenv("MQTT_TOPIC_THROTTLE"), "Mqtt topic that contains throttle value, use MQTT_TOPIC_THROTTLE if args not set") + flag.StringVar(&topicSteering, "mqtt-topic-steering", os.Getenv("MQTT_TOPIC_STEERING"), "Mqtt topic that contains steering value, use MQTT_TOPIC_STEERING if args not set") + flag.IntVar(&throttleChannel, "throttle-channel", throttleChannel, "I2C channel to use to control throttle, THROTTLE_CHANNEL env if args not set") + flag.IntVar(&steeringChannel, "steering-channel", steeringChannel, "I2C channel to use to control steering, STEERING_CHANNEL env if args not set") + flag.IntVar(&throttleStoppedPWM, "throttle-zero-pwm", throttleStoppedPWM, "Zero value for throttle PWM, THROTTLE_STOPPED_PWM env if args not set") + flag.IntVar(&throttleMinPWM, "throttle-min-pwm", throttleMinPWM, "Min value for throttle PWM, THROTTLE_MIN_PWM env if args not set") + flag.IntVar(&throttleMaxPWM, "throttle-max-pwm", throttleMaxPWM, "Max value for throttle PWM, THROTTLE_MAX_PWM env if args not set") + flag.IntVar(&steeringLeftPWM, "steering-left-pwm", steeringLeftPWM, "Max left value for steering PWM, STEERING_STOPPED_PWM env if args not set") + flag.IntVar(&steeringRightPWM, "steering-right-pwm", steeringRightPWM, "Max right value for steering PWM, STEERING_MIN_PWM env if args not set") + flag.IntVar(&updatePWMFrequency, "update-pwm-frequency", updatePWMFrequency, "Number of update values per seconds, UPDATE_PWM_FREQUENCY env if args not set") + + flag.Parse() + if len(os.Args) <= 1 { + flag.PrintDefaults() + os.Exit(1) + } + + client, err := cli.Connect(mqttBroker, username, password, clientId) + if err != nil { + log.Fatalf("unable to connect to mqtt bus: %v", err) + } + defer client.Disconnect(50) + + t := rc.NewThrottle(throttleChannel, throttleStoppedPWM, throttleMinPWM, throttleMaxPWM) + s := rc.NewSteering(steeringChannel, steeringLeftPWM, steeringRightPWM) + + p := part.NewPca9685Part(client, t, s, updatePWMFrequency, topicThrottle, topicSteering) + err = p.Start() + if err != nil { + log.Fatalf("unable to start service: %v", err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8c8f768 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/cyrilix/robocar-pca9685 + +go 1.13 + +require ( + github.com/cyrilix/robocar-base v0.0.0-20191227154304-47d48c39b0a2 + github.com/eclipse/paho.mqtt.golang v1.2.0 + periph.io/x/periph v3.6.2+incompatible +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b50d850 --- /dev/null +++ b/go.sum @@ -0,0 +1,119 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/cyrilix/robocar-base v0.0.0-20191227154304-47d48c39b0a2 h1:7E0P2+YXKtRM++vnBZtaNVlhKEMkp+X3qYdd0CzseJY= +github.com/cyrilix/robocar-base v0.0.0-20191227154304-47d48c39b0a2/go.mod h1:/KZidG8Y4sKxCCkTcswpKz20oFN3j62tJvamEHcSgLM= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible h1:dvc1KSkIYTVjZgHf/CTC2diTYC8PzhaA5sFISRfNVrE= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20190506211059-b20a14b54661 h1:ZuxGvIvF01nfc/G9RJ5Q7Va1zQE2WJyG18Zv3DqCEf4= +github.com/docker/docker v0.7.3-0.20190506211059-b20a14b54661/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-redis/redis v6.15.6+incompatible h1:H9evprGPLI8+ci7fxQx6WNZHJSb7be8FqJQRhdQZ5Sg= +github.com/go-redis/redis v6.15.6+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/testcontainers/testcontainers-go v0.0.9 h1:mwvFz+FkuQMqQ9oLkG4cVzPsZTRmrCo2NcaerJNaptA= +github.com/testcontainers/testcontainers-go v0.0.9/go.mod h1:0Qe9qqjNZgxHzzdHPWwmQ2D49FFO7920hLdJ4yUJXJI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk= +golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v0.0.0-20181223230014-1083505acf35 h1:zpdCK+REwbk+rqjJmHhiCN6iBIigrZ39glqSF0P3KF0= +gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +periph.io/x/periph v3.6.2+incompatible h1:B9vqhYVuhKtr6bXua8N9GeBEvD7yanczCvE0wU2LEqw= +periph.io/x/periph v3.6.2+incompatible/go.mod h1:EWr+FCIU2dBWz5/wSWeiIUJTriYv9v2j2ENBmgYyy7Y= diff --git a/part/part.go b/part/part.go new file mode 100644 index 0000000..1548cc9 --- /dev/null +++ b/part/part.go @@ -0,0 +1,103 @@ +package part + +import ( + "encoding/json" + "fmt" + "github.com/cyrilix/robocar-base/service" + "github.com/cyrilix/robocar-base/types" + "github.com/cyrilix/robocar-pca9685/actuator" + MQTT "github.com/eclipse/paho.mqtt.golang" + "log" + "sync" + "time" +) + +type Pca9685Part struct { + client MQTT.Client + throttleCtrl *actuator.Throttle + steeringCtrl *actuator.Steering + + muSteering sync.Mutex + steeringValue float64 + muThrottle sync.Mutex + throttleValue float64 + + updateFrequency int + + throttleTopic string + steeringTopic string +} + +func NewPca9685Part(client MQTT.Client, throttleCtrl *actuator.Throttle, steeringCtrl *actuator.Steering, updateFrequency int, throttleTopic, steeringTopic string) *Pca9685Part { + return &Pca9685Part{ + client: client, + throttleCtrl: throttleCtrl, + steeringCtrl: steeringCtrl, + updateFrequency: updateFrequency, + throttleTopic: throttleTopic, + steeringTopic: steeringTopic, + } +} + +func (p *Pca9685Part) Start() error { + if err := p.registerCallbacks(); err != nil { + return fmt.Errorf("unable to start service: %v", err) + } + defer p.Stop() + for { + time.Sleep(time.Second / time.Duration(p.updateFrequency)) + p.updateCtrl() + } +} + +func (p *Pca9685Part) Stop() { + service.StopService("pca9685", p.client, p.throttleTopic, p.steeringTopic) +} + +func (p *Pca9685Part) onThrottleChange(_ MQTT.Client, message MQTT.Message) { + var throttle types.Throttle + err := json.Unmarshal(message.Payload(), throttle) + if err != nil { + log.Printf("[%v] unable to unmarshall throttle msg: %v",message.Topic(), err) + return + } + p.muThrottle.Lock() + defer p.muThrottle.Unlock() + p.throttleCtrl.SetPercentValue(throttle.Value) +} + +func (p *Pca9685Part) onSteeringChange(_ MQTT.Client, message MQTT.Message) { + var steering types.Steering + err := json.Unmarshal(message.Payload(), steering) + if err != nil { + log.Printf("[%v] unable to unmarshall steering msg: %v",message.Topic(), err) + return + } + p.muSteering.Lock() + defer p.muSteering.Unlock() + p.steeringCtrl.SetPercentValue(steering.Value) +} + +func (p *Pca9685Part) registerCallbacks() error { + err := service.RegisterCallback(p.client, p.throttleTopic, p.onThrottleChange) + if err != nil { + return fmt.Errorf("unable to register throttle callback: %v", err) + } + + err = service.RegisterCallback(p.client, p.steeringTopic, p.onSteeringChange) + if err != nil { + return fmt.Errorf("unable to register steering callback: %v", err) + } + + return nil +} + +func (p *Pca9685Part) updateCtrl() { + p.muThrottle.Lock() + defer p.muThrottle.Unlock() + p.throttleCtrl.SetPercentValue(p.throttleValue) + + p.muSteering.Lock() + defer p.muSteering.Unlock() + p.steeringCtrl.SetPercentValue(p.steeringValue) +} diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..e52651d --- /dev/null +++ b/util/util.go @@ -0,0 +1,12 @@ +package util + +// Linear mapping between two ranges of values +func MapRange(x, xmin, xmax, ymin, ymax float64) int { + Xrange := xmax - xmin + Yrange := ymax - ymin + XYratio := Xrange / Yrange + + y := (x-xmin)/XYratio + ymin + + return int(y) +} diff --git a/util/util_test.go b/util/util_test.go new file mode 100644 index 0000000..d563b25 --- /dev/null +++ b/util/util_test.go @@ -0,0 +1,32 @@ +package util + +import "testing" + +func TestRangeMapping(t *testing.T) { + cases := []struct { + x, xmin, xmax, ymin, ymax float64 + expected int + }{ + // Test positive + {-100, -100, 100, 0, 1000, 0}, + {0, -100, 100, 0, 1000, 500}, + {100, -100, 100, 0, 1000, 1000}, + + // Test negative + {0, 0, 100, 0, 1000, 0}, + {50, 0, 100, 0, 1000, 500}, + {100, 0, 100, 0, 1000, 1000}, + + // Reverse + {0, 100, 0, 0, 1000, 1000}, + {50, 100, 0, 0, 1000, 500}, + {100, 100, 0, 0, 1000, 0}, + } + + for _, c := range cases { + val := MapRange(c.x, c.xmin, c.xmax, c.ymin, c.ymax) + if val != c.expected { + t.Errorf("MapRange(%.0f, %.0f, %.0f, %.0f, %.0f): %d, wants %d", c.x, c.xmin, c.xmax, c.ymin, c.ymax, val, c.expected) + } + } +} diff --git a/vendor/github.com/cyrilix/robocar-base/LICENSE b/vendor/github.com/cyrilix/robocar-base/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/vendor/github.com/cyrilix/robocar-base/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/cyrilix/robocar-base/cli/cli.go b/vendor/github.com/cyrilix/robocar-base/cli/cli.go new file mode 100644 index 0000000..7cfaa8f --- /dev/null +++ b/vendor/github.com/cyrilix/robocar-base/cli/cli.go @@ -0,0 +1,115 @@ +package cli + +import ( + "flag" + "fmt" + "github.com/cyrilix/robocar-base/service" + MQTT "github.com/eclipse/paho.mqtt.golang" + "log" + "os" + "os/signal" + "strconv" + "syscall" +) + +func SetDefaultValueFromEnv(value *string, key string, defaultValue string) { + if os.Getenv(key) != "" { + *value = os.Getenv(key) + } else { + *value = defaultValue + } +} +func SetIntDefaultValueFromEnv(value *int, key string, defaultValue int) error { + var sVal string + if os.Getenv(key) != "" { + sVal = os.Getenv(key) + val, err := strconv.Atoi(sVal) + if err != nil { + log.Printf("unable to convert string to int: %v", err) + return err + } + *value = val + } else { + *value = defaultValue + } + return nil +} +func SetFloat64DefaultValueFromEnv(value *float64, key string, defaultValue float64) error { + var sVal string + if os.Getenv(key) != "" { + sVal = os.Getenv(key) + val, err := strconv.ParseFloat(sVal, 64) + if err != nil { + log.Printf("unable to convert string to float: %v", err) + return err + } + *value = val + } else { + *value = defaultValue + } + return nil +} + +func HandleExit(p service.Part) { + signals := make(chan os.Signal, 1) + signal.Notify(signals, os.Kill, os.Interrupt, syscall.SIGTERM) + + go func() { + <-signals + p.Stop() + os.Exit(0) + }() +} + +func InitMqttFlags(defaultClientId string, mqttBroker, username, password, clientId *string, mqttQos *int, mqttRetain *bool) { + SetDefaultValueFromEnv(clientId, "MQTT_CLIENT_ID", defaultClientId) + SetDefaultValueFromEnv(mqttBroker, "MQTT_BROKER", "tcp://127.0.0.1:1883") + + flag.StringVar(mqttBroker, "mqtt-broker", *mqttBroker, "Broker Uri, use MQTT_BROKER env if arg not set") + flag.StringVar(username, "mqtt-username", os.Getenv("MQTT_USERNAME"), "Broker Username, use MQTT_USERNAME env if arg not set") + flag.StringVar(password, "mqtt-password", os.Getenv("MQTT_PASSWORD"), "Broker Password, MQTT_PASSWORD env if args not set") + flag.StringVar(clientId, "mqtt-client-id", *clientId, "Mqtt client id, use MQTT_CLIENT_ID env if args not set") + flag.IntVar(mqttQos, "mqtt-qos", *mqttQos, "Qos to pusblish message, use MQTT_QOS env if arg not set") + flag.BoolVar(mqttRetain, "mqtt-retain", *mqttRetain, "Retain mqtt message, if not set, true if MQTT_RETAIN env variable is set") +} + +func InitIntFlag(key string, defValue int) int { + var value int + err := SetIntDefaultValueFromEnv(&value, key, defValue) + if err != nil { + log.Panicf("invalid int value: %v", err) + } + return value +} + +func InitFloat64Flag(key string, defValue float64) float64 { + var value float64 + err := SetFloat64DefaultValueFromEnv(&value, key, defValue) + if err != nil { + log.Panicf("invalid value: %v", err) + } + return value +} + +func Connect(uri, username, password, clientId string) (MQTT.Client, error) { + //create a ClientOptions struct setting the broker address, clientid, turn + //off trace output and set the default message handler + opts := MQTT.NewClientOptions().AddBroker(uri) + opts.SetUsername(username) + opts.SetPassword(password) + opts.SetClientID(clientId) + opts.SetAutoReconnect(true) + opts.SetDefaultPublishHandler( + //define a function for the default message handler + func(client MQTT.Client, msg MQTT.Message) { + fmt.Printf("TOPIC: %s\n", msg.Topic()) + fmt.Printf("MSG: %s\n", msg.Payload()) + }) + + //create and start a client using the above ClientOptions + client := MQTT.NewClient(opts) + if token := client.Connect(); token.Wait() && token.Error() != nil { + return nil, fmt.Errorf("unable to connect to mqtt bus: %v", token.Error()) + } + return client, nil +} diff --git a/vendor/github.com/cyrilix/robocar-base/mqttdevice/mqttdevice.go b/vendor/github.com/cyrilix/robocar-base/mqttdevice/mqttdevice.go new file mode 100644 index 0000000..5a5af8a --- /dev/null +++ b/vendor/github.com/cyrilix/robocar-base/mqttdevice/mqttdevice.go @@ -0,0 +1,156 @@ +package mqttdevice + +import ( + "encoding/json" + "fmt" + "github.com/cyrilix/robocar-base/types" + MQTT "github.com/eclipse/paho.mqtt.golang" + "io" + "log" + "strconv" +) + +type Publisher interface { + Publish(topic string, payload MqttValue) +} + +type Subscriber interface { + Subscribe(topic string, mh MQTT.MessageHandler) +} + +type MQTTPubSub interface { + Publisher + Subscriber + io.Closer +} + +type pahoMqttPubSub struct { + Uri string + Username string + Password string + ClientId string + Qos int + Retain bool + client MQTT.Client +} + +func NewPahoMqttPubSub(uri string, username string, password string, clientId string, qos int, retain bool) MQTTPubSub { + p := pahoMqttPubSub{Uri: uri, Username: username, Password: password, ClientId: clientId, Qos: qos, Retain: retain} + p.Connect() + return &p +} + +// Publish message to broker +func (p *pahoMqttPubSub) Publish(topic string, payload MqttValue) { + tokenResp := p.client.Publish(topic, byte(p.Qos), p.Retain, string(payload)) + if tokenResp.Error() != nil { + log.Fatalf("%+v\n", tokenResp.Error()) + } +} + +// Register func to execute on message +func (p *pahoMqttPubSub) Subscribe(topic string, callback MQTT.MessageHandler) { + tokenResp := p.client.Subscribe(topic, byte(p.Qos), callback) + if tokenResp.Error() != nil { + log.Fatalf("%+v\n", tokenResp.Error()) + } +} + +// Close connection to broker +func (p *pahoMqttPubSub) Close() error { + p.client.Disconnect(500) + return nil +} + +func (p *pahoMqttPubSub) Connect() { + if p.client != nil && p.client.IsConnected() { + return + } + //create a ClientOptions struct setting the broker address, clientid, turn + //off trace output and set the default message handler + opts := MQTT.NewClientOptions().AddBroker(p.Uri) + opts.SetUsername(p.Username) + opts.SetPassword(p.Password) + opts.SetClientID(p.ClientId) + opts.SetAutoReconnect(true) + opts.SetDefaultPublishHandler( + //define a function for the default message handler + func(client MQTT.Client, msg MQTT.Message) { + fmt.Printf("TOPIC: %s\n", msg.Topic()) + fmt.Printf("MSG: %s\n", msg.Payload()) + }) + + //create and start a client using the above ClientOptions + p.client = MQTT.NewClient(opts) + if token := p.client.Connect(); token.Wait() && token.Error() != nil { + panic(token.Error()) + } +} + +type MqttValue []byte + +func NewMqttValue(v interface{}) MqttValue { + switch val := v.(type) { + case string: + return MqttValue(val) + case float32, float64: + return MqttValue(fmt.Sprintf("%0.2f", val)) + case int, int8, int16, int32, int64: + return MqttValue(fmt.Sprintf("%d", val)) + case bool: + if val { + return []byte("ON") + } else { + return []byte("OFF") + } + case []byte: + return val + case MqttValue: + return val + default: + jsonValue, err := json.Marshal(v) + if err != nil { + log.Printf("unable to mashall to json value '%v': %v", v, err) + return nil + } + return jsonValue + } +} + +func (m *MqttValue) IntValue() (int, error) { + return strconv.Atoi(string(*m)) +} + +func (m *MqttValue) Float32Value() (float32, error) { + val := string(*m) + r, err := strconv.ParseFloat(val, 32) + return float32(r), err +} +func (m *MqttValue) Float64Value() (float64, error) { + val := string(*m) + return strconv.ParseFloat(val, 64) +} +func (m *MqttValue) StringValue() (string, error) { + return string(*m), nil +} +func (m *MqttValue) DriveModeValue() (types.DriveMode, error) { + val, err := m.IntValue() + if err != nil { + return types.DriveModeInvalid, err + } + return types.DriveMode(val), nil +} +func (m *MqttValue) ByteSliceValue() ([]byte, error) { + return *m, nil +} +func (m *MqttValue) BoolValue() (bool, error) { + val := string(*m) + switch val { + case "ON": + return true, nil + case "OFF": + return false, nil + default: + return false, fmt.Errorf("value %v can't be converted to bool", val) + } +} diff --git a/vendor/github.com/cyrilix/robocar-base/service/part.go b/vendor/github.com/cyrilix/robocar-base/service/part.go new file mode 100644 index 0000000..c370778 --- /dev/null +++ b/vendor/github.com/cyrilix/robocar-base/service/part.go @@ -0,0 +1,33 @@ +package service + +import ( + "fmt" + mqtt "github.com/eclipse/paho.mqtt.golang" + "log" +) + +func StopService(name string, client mqtt.Client, topics ...string) { + log.Printf("Stop %s service", name) + token := client.Unsubscribe(topics...) + token.Wait() + if token.Error() != nil { + log.Printf("unable to unsubscribe service: %v", token.Error()) + } + client.Disconnect(50) +} + +func RegisterCallback(client mqtt.Client, topic string, callback mqtt.MessageHandler) error { + log.Printf("Register callback on topic %v", topic) + token := client.Subscribe(topic, 0, callback) + token.Wait() + if token.Error() != nil { + return fmt.Errorf("unable to register callback on topic %s: %v", topic, token.Error()) + } + return nil +} + +type Part interface { + Start() error + Stop() +} + diff --git a/vendor/github.com/cyrilix/robocar-base/types/mode.go b/vendor/github.com/cyrilix/robocar-base/types/mode.go new file mode 100644 index 0000000..de839a9 --- /dev/null +++ b/vendor/github.com/cyrilix/robocar-base/types/mode.go @@ -0,0 +1,36 @@ +package types + +import ( + "log" +) + +type DriveMode int + +const ( + DriveModeInvalid = -1 + DriveModeUser = iota + DriveModePilot +) + +func ToString(mode DriveMode) string { + switch mode { + case DriveModeUser: + return "user" + case DriveModePilot: + return "pilot" + default: + return "" + } +} + +func ParseString(val string) DriveMode { + switch val { + case "user": + return DriveModeUser + case "pilot": + return DriveModePilot + default: + log.Printf("invalid DriveMode: %v", val) + return DriveModeInvalid + } +} diff --git a/vendor/github.com/cyrilix/robocar-base/types/rc.go b/vendor/github.com/cyrilix/robocar-base/types/rc.go new file mode 100644 index 0000000..6568d92 --- /dev/null +++ b/vendor/github.com/cyrilix/robocar-base/types/rc.go @@ -0,0 +1,10 @@ +package types + +/* Radio control value */ +type RCValue struct { + Value float64 + Confidence float64 +} + +type Steering RCValue +type Throttle RCValue diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/.gitignore b/vendor/github.com/eclipse/paho.mqtt.golang/.gitignore new file mode 100644 index 0000000..47bb0de --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/.gitignore @@ -0,0 +1,36 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe + +*.msg +*.lok + +samples/trivial +samples/trivial2 +samples/sample +samples/reconnect +samples/ssl +samples/custom_store +samples/simple +samples/stdinpub +samples/stdoutsub +samples/routing \ No newline at end of file diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/CONTRIBUTING.md b/vendor/github.com/eclipse/paho.mqtt.golang/CONTRIBUTING.md new file mode 100644 index 0000000..9791dc6 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/CONTRIBUTING.md @@ -0,0 +1,56 @@ +Contributing to Paho +==================== + +Thanks for your interest in this project. + +Project description: +-------------------- + +The Paho project has been created to provide scalable open-source implementations of open and standard messaging protocols aimed at new, existing, and emerging applications for Machine-to-Machine (M2M) and Internet of Things (IoT). +Paho reflects the inherent physical and cost constraints of device connectivity. Its objectives include effective levels of decoupling between devices and applications, designed to keep markets open and encourage the rapid growth of scalable Web and Enterprise middleware and applications. Paho is being kicked off with MQTT publish/subscribe client implementations for use on embedded platforms, along with corresponding server support as determined by the community. + +- https://projects.eclipse.org/projects/technology.paho + +Developer resources: +-------------------- + +Information regarding source code management, builds, coding standards, and more. + +- https://projects.eclipse.org/projects/technology.paho/developer + +Contributor License Agreement: +------------------------------ + +Before your contribution can be accepted by the project, you need to create and electronically sign the Eclipse Foundation Contributor License Agreement (CLA). + +- http://www.eclipse.org/legal/CLA.php + +Contributing Code: +------------------ + +The Go client is developed in Github, see their documentation on the process of forking and pull requests; https://help.github.com/categories/collaborating-on-projects-using-pull-requests/ + +Git commit messages should follow the style described here; + +http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html + +Contact: +-------- + +Contact the project developers via the project's "dev" list. + +- https://dev.eclipse.org/mailman/listinfo/paho-dev + +Search for bugs: +---------------- + +This project uses Github issues to track ongoing development and issues. + +- https://github.com/eclipse/paho.mqtt.golang/issues + +Create a new bug: +----------------- + +Be sure to search for existing bugs before you create another one. Remember that contributions are always welcome! + +- https://github.com/eclipse/paho.mqtt.golang/issues diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/DISTRIBUTION b/vendor/github.com/eclipse/paho.mqtt.golang/DISTRIBUTION new file mode 100644 index 0000000..34e4973 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/DISTRIBUTION @@ -0,0 +1,15 @@ + + +Eclipse Distribution License - v 1.0 + +Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + Neither the name of the Eclipse Foundation, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/LICENSE b/vendor/github.com/eclipse/paho.mqtt.golang/LICENSE new file mode 100644 index 0000000..aa7cc81 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/LICENSE @@ -0,0 +1,87 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and + +b) in the case of each subsequent Contributor: + +i) changes to the Program, and + +ii) additions to the Program; + +where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. + +2. GRANT OF RIGHTS + +a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. + +b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. + +c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. + +d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: + +a) it complies with the terms and conditions of this Agreement; and + +b) its license agreement: + +i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; + +ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; + +iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and + +iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + +a) it must be made available under this Agreement; and + +b) a copy of this Agreement must be included with each copy of the Program. + +Contributors may not remove or alter any copyright notices contained within the Program. + +Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. \ No newline at end of file diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/README.md b/vendor/github.com/eclipse/paho.mqtt.golang/README.md new file mode 100644 index 0000000..81c7148 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/README.md @@ -0,0 +1,67 @@ + +[![GoDoc](https://godoc.org/github.com/eclipse/paho.mqtt.golang?status.svg)](https://godoc.org/github.com/eclipse/paho.mqtt.golang) +[![Go Report Card](https://goreportcard.com/badge/github.com/eclipse/paho.mqtt.golang)](https://goreportcard.com/report/github.com/eclipse/paho.mqtt.golang) + +Eclipse Paho MQTT Go client +=========================== + + +This repository contains the source code for the [Eclipse Paho](http://eclipse.org/paho) MQTT Go client library. + +This code builds a library which enable applications to connect to an [MQTT](http://mqtt.org) broker to publish messages, and to subscribe to topics and receive published messages. + +This library supports a fully asynchronous mode of operation. + + +Installation and Build +---------------------- + +This client is designed to work with the standard Go tools, so installation is as easy as: + +``` +go get github.com/eclipse/paho.mqtt.golang +``` + +The client depends on Google's [websockets](https://godoc.org/golang.org/x/net/websocket) and [proxy](https://godoc.org/golang.org/x/net/proxy) package, +also easily installed with the commands: + +``` +go get golang.org/x/net/websocket +go get golang.org/x/net/proxy +``` + + +Usage and API +------------- + +Detailed API documentation is available by using to godoc tool, or can be browsed online +using the [godoc.org](http://godoc.org/github.com/eclipse/paho.mqtt.golang) service. + +Make use of the library by importing it in your Go client source code. For example, +``` +import "github.com/eclipse/paho.mqtt.golang" +``` + +Samples are available in the `cmd` directory for reference. + + +Runtime tracing +--------------- + +Tracing is enabled by assigning logs (from the Go log package) to the logging endpoints, ERROR, CRITICAL, WARN and DEBUG + + +Reporting bugs +-------------- + +Please report bugs by raising issues for this project in github https://github.com/eclipse/paho.mqtt.golang/issues + + +More information +---------------- + +Discussion of the Paho clients takes place on the [Eclipse paho-dev mailing list](https://dev.eclipse.org/mailman/listinfo/paho-dev). + +General questions about the MQTT protocol are discussed in the [MQTT Google Group](https://groups.google.com/forum/?hl=en-US&fromgroups#!forum/mqtt). + +There is much more information available via the [MQTT community site](http://mqtt.org). diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/about.html b/vendor/github.com/eclipse/paho.mqtt.golang/about.html new file mode 100644 index 0000000..b183f41 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/about.html @@ -0,0 +1,41 @@ + + + +About + + +

About This Content

+ +

December 9, 2013

+

License

+ +

The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise +indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 1.0 ("EPL") and Eclipse Distribution License Version 1.0 ("EDL"). +A copy of the EPL is available at +http://www.eclipse.org/legal/epl-v10.html +and a copy of the EDL is available at +http://www.eclipse.org/org/documents/edl-v10.php. +For purposes of the EPL, "Program" will mean the Content.

+ +

If you did not receive this Content directly from the Eclipse Foundation, the Content is +being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was +provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content +and such source code may be obtained at http://www.eclipse.org.

+ + +

Third Party Content

+

The Content includes items that have been sourced from third parties as set out below. If you + did not receive this Content directly from the Eclipse Foundation, the following is provided + for informational purposes only, and you should look to the Redistributor's license for + terms and conditions of use.

+

+ None

+

+

+ + + + diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/client.go b/vendor/github.com/eclipse/paho.mqtt.golang/client.go new file mode 100644 index 0000000..24d56c1 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/client.go @@ -0,0 +1,759 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +// Portions copyright © 2018 TIBCO Software Inc. + +// Package mqtt provides an MQTT v3.1.1 client library. +package mqtt + +import ( + "errors" + "fmt" + "net" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/eclipse/paho.mqtt.golang/packets" +) + +const ( + disconnected uint32 = iota + connecting + reconnecting + connected +) + +// Client is the interface definition for a Client as used by this +// library, the interface is primarily to allow mocking tests. +// +// It is an MQTT v3.1.1 client for communicating +// with an MQTT server using non-blocking methods that allow work +// to be done in the background. +// An application may connect to an MQTT server using: +// A plain TCP socket +// A secure SSL/TLS socket +// A websocket +// To enable ensured message delivery at Quality of Service (QoS) levels +// described in the MQTT spec, a message persistence mechanism must be +// used. This is done by providing a type which implements the Store +// interface. For convenience, FileStore and MemoryStore are provided +// implementations that should be sufficient for most use cases. More +// information can be found in their respective documentation. +// Numerous connection options may be specified by configuring a +// and then supplying a ClientOptions type. +type Client interface { + // IsConnected returns a bool signifying whether + // the client is connected or not. + IsConnected() bool + // IsConnectionOpen return a bool signifying wether the client has an active + // connection to mqtt broker, i.e not in disconnected or reconnect mode + IsConnectionOpen() bool + // Connect will create a connection to the message broker, by default + // it will attempt to connect at v3.1.1 and auto retry at v3.1 if that + // fails + Connect() Token + // Disconnect will end the connection with the server, but not before waiting + // the specified number of milliseconds to wait for existing work to be + // completed. + Disconnect(quiesce uint) + // Publish will publish a message with the specified QoS and content + // to the specified topic. + // Returns a token to track delivery of the message to the broker + Publish(topic string, qos byte, retained bool, payload interface{}) Token + // Subscribe starts a new subscription. Provide a MessageHandler to be executed when + // a message is published on the topic provided, or nil for the default handler + Subscribe(topic string, qos byte, callback MessageHandler) Token + // SubscribeMultiple starts a new subscription for multiple topics. Provide a MessageHandler to + // be executed when a message is published on one of the topics provided, or nil for the + // default handler + SubscribeMultiple(filters map[string]byte, callback MessageHandler) Token + // Unsubscribe will end the subscription from each of the topics provided. + // Messages published to those topics from other clients will no longer be + // received. + Unsubscribe(topics ...string) Token + // AddRoute allows you to add a handler for messages on a specific topic + // without making a subscription. For example having a different handler + // for parts of a wildcard subscription + AddRoute(topic string, callback MessageHandler) + // OptionsReader returns a ClientOptionsReader which is a copy of the clientoptions + // in use by the client. + OptionsReader() ClientOptionsReader +} + +// client implements the Client interface +type client struct { + lastSent atomic.Value + lastReceived atomic.Value + pingOutstanding int32 + status uint32 + sync.RWMutex + messageIds + conn net.Conn + ibound chan packets.ControlPacket + obound chan *PacketAndToken + oboundP chan *PacketAndToken + msgRouter *router + stopRouter chan bool + incomingPubChan chan *packets.PublishPacket + errors chan error + stop chan struct{} + persist Store + options ClientOptions + workers sync.WaitGroup +} + +// NewClient will create an MQTT v3.1.1 client with all of the options specified +// in the provided ClientOptions. The client must have the Connect method called +// on it before it may be used. This is to make sure resources (such as a net +// connection) are created before the application is actually ready. +func NewClient(o *ClientOptions) Client { + c := &client{} + c.options = *o + + if c.options.Store == nil { + c.options.Store = NewMemoryStore() + } + switch c.options.ProtocolVersion { + case 3, 4: + c.options.protocolVersionExplicit = true + case 0x83, 0x84: + c.options.protocolVersionExplicit = true + default: + c.options.ProtocolVersion = 4 + c.options.protocolVersionExplicit = false + } + c.persist = c.options.Store + c.status = disconnected + c.messageIds = messageIds{index: make(map[uint16]tokenCompletor)} + c.msgRouter, c.stopRouter = newRouter() + c.msgRouter.setDefaultHandler(c.options.DefaultPublishHandler) + if !c.options.AutoReconnect { + c.options.MessageChannelDepth = 0 + } + return c +} + +// AddRoute allows you to add a handler for messages on a specific topic +// without making a subscription. For example having a different handler +// for parts of a wildcard subscription +func (c *client) AddRoute(topic string, callback MessageHandler) { + if callback != nil { + c.msgRouter.addRoute(topic, callback) + } +} + +// IsConnected returns a bool signifying whether +// the client is connected or not. +func (c *client) IsConnected() bool { + c.RLock() + defer c.RUnlock() + status := atomic.LoadUint32(&c.status) + switch { + case status == connected: + return true + case c.options.AutoReconnect && status > connecting: + return true + default: + return false + } +} + +// IsConnectionOpen return a bool signifying whether the client has an active +// connection to mqtt broker, i.e not in disconnected or reconnect mode +func (c *client) IsConnectionOpen() bool { + c.RLock() + defer c.RUnlock() + status := atomic.LoadUint32(&c.status) + switch { + case status == connected: + return true + default: + return false + } +} + +func (c *client) connectionStatus() uint32 { + c.RLock() + defer c.RUnlock() + status := atomic.LoadUint32(&c.status) + return status +} + +func (c *client) setConnected(status uint32) { + c.Lock() + defer c.Unlock() + atomic.StoreUint32(&c.status, uint32(status)) +} + +//ErrNotConnected is the error returned from function calls that are +//made when the client is not connected to a broker +var ErrNotConnected = errors.New("Not Connected") + +// Connect will create a connection to the message broker, by default +// it will attempt to connect at v3.1.1 and auto retry at v3.1 if that +// fails +func (c *client) Connect() Token { + var err error + t := newToken(packets.Connect).(*ConnectToken) + DEBUG.Println(CLI, "Connect()") + + c.obound = make(chan *PacketAndToken, c.options.MessageChannelDepth) + c.oboundP = make(chan *PacketAndToken, c.options.MessageChannelDepth) + c.ibound = make(chan packets.ControlPacket) + + go func() { + c.persist.Open() + + c.setConnected(connecting) + c.errors = make(chan error, 1) + c.stop = make(chan struct{}) + + var rc byte + protocolVersion := c.options.ProtocolVersion + + if len(c.options.Servers) == 0 { + t.setError(fmt.Errorf("No servers defined to connect to")) + return + } + + for _, broker := range c.options.Servers { + cm := newConnectMsgFromOptions(&c.options, broker) + c.options.ProtocolVersion = protocolVersion + CONN: + DEBUG.Println(CLI, "about to write new connect msg") + c.conn, err = openConnection(broker, c.options.TLSConfig, c.options.ConnectTimeout, c.options.HTTPHeaders) + if err == nil { + DEBUG.Println(CLI, "socket connected to broker") + switch c.options.ProtocolVersion { + case 3: + DEBUG.Println(CLI, "Using MQTT 3.1 protocol") + cm.ProtocolName = "MQIsdp" + cm.ProtocolVersion = 3 + case 0x83: + DEBUG.Println(CLI, "Using MQTT 3.1b protocol") + cm.ProtocolName = "MQIsdp" + cm.ProtocolVersion = 0x83 + case 0x84: + DEBUG.Println(CLI, "Using MQTT 3.1.1b protocol") + cm.ProtocolName = "MQTT" + cm.ProtocolVersion = 0x84 + default: + DEBUG.Println(CLI, "Using MQTT 3.1.1 protocol") + c.options.ProtocolVersion = 4 + cm.ProtocolName = "MQTT" + cm.ProtocolVersion = 4 + } + cm.Write(c.conn) + + rc, t.sessionPresent = c.connect() + if rc != packets.Accepted { + if c.conn != nil { + c.conn.Close() + c.conn = nil + } + //if the protocol version was explicitly set don't do any fallback + if c.options.protocolVersionExplicit { + ERROR.Println(CLI, "Connecting to", broker, "CONNACK was not CONN_ACCEPTED, but rather", packets.ConnackReturnCodes[rc]) + continue + } + if c.options.ProtocolVersion == 4 { + DEBUG.Println(CLI, "Trying reconnect using MQTT 3.1 protocol") + c.options.ProtocolVersion = 3 + goto CONN + } + } + break + } else { + ERROR.Println(CLI, err.Error()) + WARN.Println(CLI, "failed to connect to broker, trying next") + rc = packets.ErrNetworkError + } + } + + if c.conn == nil { + ERROR.Println(CLI, "Failed to connect to a broker") + c.setConnected(disconnected) + c.persist.Close() + t.returnCode = rc + if rc != packets.ErrNetworkError { + t.setError(packets.ConnErrors[rc]) + } else { + t.setError(fmt.Errorf("%s : %s", packets.ConnErrors[rc], err)) + } + return + } + + c.options.protocolVersionExplicit = true + + if c.options.KeepAlive != 0 { + atomic.StoreInt32(&c.pingOutstanding, 0) + c.lastReceived.Store(time.Now()) + c.lastSent.Store(time.Now()) + c.workers.Add(1) + go keepalive(c) + } + + c.incomingPubChan = make(chan *packets.PublishPacket, c.options.MessageChannelDepth) + c.msgRouter.matchAndDispatch(c.incomingPubChan, c.options.Order, c) + + c.setConnected(connected) + DEBUG.Println(CLI, "client is connected") + if c.options.OnConnect != nil { + go c.options.OnConnect(c) + } + + c.workers.Add(4) + go errorWatch(c) + go alllogic(c) + go outgoing(c) + go incoming(c) + + // Take care of any messages in the store + if c.options.CleanSession == false { + c.resume(c.options.ResumeSubs) + } else { + c.persist.Reset() + } + + DEBUG.Println(CLI, "exit startClient") + t.flowComplete() + }() + return t +} + +// internal function used to reconnect the client when it loses its connection +func (c *client) reconnect() { + DEBUG.Println(CLI, "enter reconnect") + var ( + err error + + rc = byte(1) + sleep = time.Duration(1 * time.Second) + ) + + for rc != 0 && atomic.LoadUint32(&c.status) != disconnected { + for _, broker := range c.options.Servers { + cm := newConnectMsgFromOptions(&c.options, broker) + DEBUG.Println(CLI, "about to write new connect msg") + c.Lock() + c.conn, err = openConnection(broker, c.options.TLSConfig, c.options.ConnectTimeout, c.options.HTTPHeaders) + c.Unlock() + if err == nil { + DEBUG.Println(CLI, "socket connected to broker") + switch c.options.ProtocolVersion { + case 0x83: + DEBUG.Println(CLI, "Using MQTT 3.1b protocol") + cm.ProtocolName = "MQIsdp" + cm.ProtocolVersion = 0x83 + case 0x84: + DEBUG.Println(CLI, "Using MQTT 3.1.1b protocol") + cm.ProtocolName = "MQTT" + cm.ProtocolVersion = 0x84 + case 3: + DEBUG.Println(CLI, "Using MQTT 3.1 protocol") + cm.ProtocolName = "MQIsdp" + cm.ProtocolVersion = 3 + default: + DEBUG.Println(CLI, "Using MQTT 3.1.1 protocol") + cm.ProtocolName = "MQTT" + cm.ProtocolVersion = 4 + } + cm.Write(c.conn) + + rc, _ = c.connect() + if rc != packets.Accepted { + c.conn.Close() + c.conn = nil + //if the protocol version was explicitly set don't do any fallback + if c.options.protocolVersionExplicit { + ERROR.Println(CLI, "Connecting to", broker, "CONNACK was not Accepted, but rather", packets.ConnackReturnCodes[rc]) + continue + } + } + break + } else { + ERROR.Println(CLI, err.Error()) + WARN.Println(CLI, "failed to connect to broker, trying next") + rc = packets.ErrNetworkError + } + } + if rc != 0 { + DEBUG.Println(CLI, "Reconnect failed, sleeping for", int(sleep.Seconds()), "seconds") + time.Sleep(sleep) + if sleep < c.options.MaxReconnectInterval { + sleep *= 2 + } + + if sleep > c.options.MaxReconnectInterval { + sleep = c.options.MaxReconnectInterval + } + } + } + // Disconnect() must have been called while we were trying to reconnect. + if c.connectionStatus() == disconnected { + DEBUG.Println(CLI, "Client moved to disconnected state while reconnecting, abandoning reconnect") + return + } + + c.stop = make(chan struct{}) + + if c.options.KeepAlive != 0 { + atomic.StoreInt32(&c.pingOutstanding, 0) + c.lastReceived.Store(time.Now()) + c.lastSent.Store(time.Now()) + c.workers.Add(1) + go keepalive(c) + } + + c.setConnected(connected) + DEBUG.Println(CLI, "client is reconnected") + if c.options.OnConnect != nil { + go c.options.OnConnect(c) + } + + c.workers.Add(4) + go errorWatch(c) + go alllogic(c) + go outgoing(c) + go incoming(c) + + c.resume(false) +} + +// This function is only used for receiving a connack +// when the connection is first started. +// This prevents receiving incoming data while resume +// is in progress if clean session is false. +func (c *client) connect() (byte, bool) { + DEBUG.Println(NET, "connect started") + + ca, err := packets.ReadPacket(c.conn) + if err != nil { + ERROR.Println(NET, "connect got error", err) + return packets.ErrNetworkError, false + } + if ca == nil { + ERROR.Println(NET, "received nil packet") + return packets.ErrNetworkError, false + } + + msg, ok := ca.(*packets.ConnackPacket) + if !ok { + ERROR.Println(NET, "received msg that was not CONNACK") + return packets.ErrNetworkError, false + } + + DEBUG.Println(NET, "received connack") + return msg.ReturnCode, msg.SessionPresent +} + +// Disconnect will end the connection with the server, but not before waiting +// the specified number of milliseconds to wait for existing work to be +// completed. +func (c *client) Disconnect(quiesce uint) { + status := atomic.LoadUint32(&c.status) + if status == connected { + DEBUG.Println(CLI, "disconnecting") + c.setConnected(disconnected) + + dm := packets.NewControlPacket(packets.Disconnect).(*packets.DisconnectPacket) + dt := newToken(packets.Disconnect) + c.oboundP <- &PacketAndToken{p: dm, t: dt} + + // wait for work to finish, or quiesce time consumed + dt.WaitTimeout(time.Duration(quiesce) * time.Millisecond) + } else { + WARN.Println(CLI, "Disconnect() called but not connected (disconnected/reconnecting)") + c.setConnected(disconnected) + } + + c.disconnect() +} + +// ForceDisconnect will end the connection with the mqtt broker immediately. +func (c *client) forceDisconnect() { + if !c.IsConnected() { + WARN.Println(CLI, "already disconnected") + return + } + c.setConnected(disconnected) + c.conn.Close() + DEBUG.Println(CLI, "forcefully disconnecting") + c.disconnect() +} + +func (c *client) internalConnLost(err error) { + // Only do anything if this was called and we are still "connected" + // forceDisconnect can cause incoming/outgoing/alllogic to end with + // error from closing the socket but state will be "disconnected" + if c.IsConnected() { + c.closeStop() + c.conn.Close() + c.workers.Wait() + if c.options.CleanSession && !c.options.AutoReconnect { + c.messageIds.cleanUp() + } + if c.options.AutoReconnect { + c.setConnected(reconnecting) + go c.reconnect() + } else { + c.setConnected(disconnected) + } + if c.options.OnConnectionLost != nil { + go c.options.OnConnectionLost(c, err) + } + } +} + +func (c *client) closeStop() { + c.Lock() + defer c.Unlock() + select { + case <-c.stop: + DEBUG.Println("In disconnect and stop channel is already closed") + default: + if c.stop != nil { + close(c.stop) + } + } +} + +func (c *client) closeStopRouter() { + c.Lock() + defer c.Unlock() + select { + case <-c.stopRouter: + DEBUG.Println("In disconnect and stop channel is already closed") + default: + if c.stopRouter != nil { + close(c.stopRouter) + } + } +} + +func (c *client) closeConn() { + c.Lock() + defer c.Unlock() + if c.conn != nil { + c.conn.Close() + } +} + +func (c *client) disconnect() { + c.closeStop() + c.closeConn() + c.workers.Wait() + c.messageIds.cleanUp() + c.closeStopRouter() + DEBUG.Println(CLI, "disconnected") + c.persist.Close() +} + +// Publish will publish a message with the specified QoS and content +// to the specified topic. +// Returns a token to track delivery of the message to the broker +func (c *client) Publish(topic string, qos byte, retained bool, payload interface{}) Token { + token := newToken(packets.Publish).(*PublishToken) + DEBUG.Println(CLI, "enter Publish") + switch { + case !c.IsConnected(): + token.setError(ErrNotConnected) + return token + case c.connectionStatus() == reconnecting && qos == 0: + token.flowComplete() + return token + } + pub := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) + pub.Qos = qos + pub.TopicName = topic + pub.Retain = retained + switch payload.(type) { + case string: + pub.Payload = []byte(payload.(string)) + case []byte: + pub.Payload = payload.([]byte) + default: + token.setError(fmt.Errorf("Unknown payload type")) + return token + } + + if pub.Qos != 0 && pub.MessageID == 0 { + pub.MessageID = c.getID(token) + token.messageID = pub.MessageID + } + persistOutbound(c.persist, pub) + if c.connectionStatus() == reconnecting { + DEBUG.Println(CLI, "storing publish message (reconnecting), topic:", topic) + } else { + DEBUG.Println(CLI, "sending publish message, topic:", topic) + c.obound <- &PacketAndToken{p: pub, t: token} + } + return token +} + +// Subscribe starts a new subscription. Provide a MessageHandler to be executed when +// a message is published on the topic provided. +func (c *client) Subscribe(topic string, qos byte, callback MessageHandler) Token { + token := newToken(packets.Subscribe).(*SubscribeToken) + DEBUG.Println(CLI, "enter Subscribe") + if !c.IsConnected() { + token.setError(ErrNotConnected) + return token + } + sub := packets.NewControlPacket(packets.Subscribe).(*packets.SubscribePacket) + if err := validateTopicAndQos(topic, qos); err != nil { + token.setError(err) + return token + } + sub.Topics = append(sub.Topics, topic) + sub.Qoss = append(sub.Qoss, qos) + DEBUG.Println(CLI, sub.String()) + + if strings.HasPrefix(topic, "$share") { + topic = strings.Join(strings.Split(topic, "/")[2:], "/") + } + + if callback != nil { + c.msgRouter.addRoute(topic, callback) + } + + token.subs = append(token.subs, topic) + c.oboundP <- &PacketAndToken{p: sub, t: token} + DEBUG.Println(CLI, "exit Subscribe") + return token +} + +// SubscribeMultiple starts a new subscription for multiple topics. Provide a MessageHandler to +// be executed when a message is published on one of the topics provided. +func (c *client) SubscribeMultiple(filters map[string]byte, callback MessageHandler) Token { + var err error + token := newToken(packets.Subscribe).(*SubscribeToken) + DEBUG.Println(CLI, "enter SubscribeMultiple") + if !c.IsConnected() { + token.setError(ErrNotConnected) + return token + } + sub := packets.NewControlPacket(packets.Subscribe).(*packets.SubscribePacket) + if sub.Topics, sub.Qoss, err = validateSubscribeMap(filters); err != nil { + token.setError(err) + return token + } + + if callback != nil { + for topic := range filters { + c.msgRouter.addRoute(topic, callback) + } + } + token.subs = make([]string, len(sub.Topics)) + copy(token.subs, sub.Topics) + c.oboundP <- &PacketAndToken{p: sub, t: token} + DEBUG.Println(CLI, "exit SubscribeMultiple") + return token +} + +// Load all stored messages and resend them +// Call this to ensure QOS > 1,2 even after an application crash +func (c *client) resume(subscription bool) { + + storedKeys := c.persist.All() + for _, key := range storedKeys { + packet := c.persist.Get(key) + if packet == nil { + continue + } + details := packet.Details() + if isKeyOutbound(key) { + switch packet.(type) { + case *packets.SubscribePacket: + if subscription { + DEBUG.Println(STR, fmt.Sprintf("loaded pending subscribe (%d)", details.MessageID)) + token := newToken(packets.Subscribe).(*SubscribeToken) + c.oboundP <- &PacketAndToken{p: packet, t: token} + } + case *packets.UnsubscribePacket: + if subscription { + DEBUG.Println(STR, fmt.Sprintf("loaded pending unsubscribe (%d)", details.MessageID)) + token := newToken(packets.Unsubscribe).(*UnsubscribeToken) + c.oboundP <- &PacketAndToken{p: packet, t: token} + } + case *packets.PubrelPacket: + DEBUG.Println(STR, fmt.Sprintf("loaded pending pubrel (%d)", details.MessageID)) + select { + case c.oboundP <- &PacketAndToken{p: packet, t: nil}: + case <-c.stop: + } + case *packets.PublishPacket: + token := newToken(packets.Publish).(*PublishToken) + token.messageID = details.MessageID + c.claimID(token, details.MessageID) + DEBUG.Println(STR, fmt.Sprintf("loaded pending publish (%d)", details.MessageID)) + DEBUG.Println(STR, details) + c.obound <- &PacketAndToken{p: packet, t: token} + default: + ERROR.Println(STR, "invalid message type in store (discarded)") + c.persist.Del(key) + } + } else { + switch packet.(type) { + case *packets.PubrelPacket, *packets.PublishPacket: + DEBUG.Println(STR, fmt.Sprintf("loaded pending incomming (%d)", details.MessageID)) + select { + case c.ibound <- packet: + case <-c.stop: + } + default: + ERROR.Println(STR, "invalid message type in store (discarded)") + c.persist.Del(key) + } + } + } +} + +// Unsubscribe will end the subscription from each of the topics provided. +// Messages published to those topics from other clients will no longer be +// received. +func (c *client) Unsubscribe(topics ...string) Token { + token := newToken(packets.Unsubscribe).(*UnsubscribeToken) + DEBUG.Println(CLI, "enter Unsubscribe") + if !c.IsConnected() { + token.setError(ErrNotConnected) + return token + } + unsub := packets.NewControlPacket(packets.Unsubscribe).(*packets.UnsubscribePacket) + unsub.Topics = make([]string, len(topics)) + copy(unsub.Topics, topics) + + c.oboundP <- &PacketAndToken{p: unsub, t: token} + for _, topic := range topics { + c.msgRouter.deleteRoute(topic) + } + + DEBUG.Println(CLI, "exit Unsubscribe") + return token +} + +// OptionsReader returns a ClientOptionsReader which is a copy of the clientoptions +// in use by the client. +func (c *client) OptionsReader() ClientOptionsReader { + r := ClientOptionsReader{options: &c.options} + return r +} + +//DefaultConnectionLostHandler is a definition of a function that simply +//reports to the DEBUG log the reason for the client losing a connection. +func DefaultConnectionLostHandler(client Client, reason error) { + DEBUG.Println("Connection lost:", reason.Error()) +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/components.go b/vendor/github.com/eclipse/paho.mqtt.golang/components.go new file mode 100644 index 0000000..01f5faf --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/components.go @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +package mqtt + +type component string + +// Component names for debug output +const ( + NET component = "[net] " + PNG component = "[pinger] " + CLI component = "[client] " + DEC component = "[decode] " + MES component = "[message] " + STR component = "[store] " + MID component = "[msgids] " + TST component = "[test] " + STA component = "[state] " + ERR component = "[error] " +) diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/edl-v10 b/vendor/github.com/eclipse/paho.mqtt.golang/edl-v10 new file mode 100644 index 0000000..cf989f1 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/edl-v10 @@ -0,0 +1,15 @@ + +Eclipse Distribution License - v 1.0 + +Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + Neither the name of the Eclipse Foundation, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/epl-v10 b/vendor/github.com/eclipse/paho.mqtt.golang/epl-v10 new file mode 100644 index 0000000..79e486c --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/epl-v10 @@ -0,0 +1,70 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and +b) in the case of each subsequent Contributor: +i) changes to the Program, and +ii) additions to the Program; +where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. + +2. GRANT OF RIGHTS + +a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. +b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. +c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. +d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: + +a) it complies with the terms and conditions of this Agreement; and +b) its license agreement: +i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; +ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; +iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and +iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. +When the Program is made available in source code form: + +a) it must be made available under this Agreement; and +b) a copy of this Agreement must be included with each copy of the Program. +Contributors may not remove or alter any copyright notices contained within the Program. + +Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/filestore.go b/vendor/github.com/eclipse/paho.mqtt.golang/filestore.go new file mode 100644 index 0000000..c4a0d36 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/filestore.go @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +package mqtt + +import ( + "io/ioutil" + "os" + "path" + "sort" + "sync" + + "github.com/eclipse/paho.mqtt.golang/packets" +) + +const ( + msgExt = ".msg" + tmpExt = ".tmp" + corruptExt = ".CORRUPT" +) + +// FileStore implements the store interface using the filesystem to provide +// true persistence, even across client failure. This is designed to use a +// single directory per running client. If you are running multiple clients +// on the same filesystem, you will need to be careful to specify unique +// store directories for each. +type FileStore struct { + sync.RWMutex + directory string + opened bool +} + +// NewFileStore will create a new FileStore which stores its messages in the +// directory provided. +func NewFileStore(directory string) *FileStore { + store := &FileStore{ + directory: directory, + opened: false, + } + return store +} + +// Open will allow the FileStore to be used. +func (store *FileStore) Open() { + store.Lock() + defer store.Unlock() + // if no store directory was specified in ClientOpts, by default use the + // current working directory + if store.directory == "" { + store.directory, _ = os.Getwd() + } + + // if store dir exists, great, otherwise, create it + if !exists(store.directory) { + perms := os.FileMode(0770) + merr := os.MkdirAll(store.directory, perms) + chkerr(merr) + } + store.opened = true + DEBUG.Println(STR, "store is opened at", store.directory) +} + +// Close will disallow the FileStore from being used. +func (store *FileStore) Close() { + store.Lock() + defer store.Unlock() + store.opened = false + DEBUG.Println(STR, "store is closed") +} + +// Put will put a message into the store, associated with the provided +// key value. +func (store *FileStore) Put(key string, m packets.ControlPacket) { + store.Lock() + defer store.Unlock() + if !store.opened { + ERROR.Println(STR, "Trying to use file store, but not open") + return + } + full := fullpath(store.directory, key) + write(store.directory, key, m) + if !exists(full) { + ERROR.Println(STR, "file not created:", full) + } +} + +// Get will retrieve a message from the store, the one associated with +// the provided key value. +func (store *FileStore) Get(key string) packets.ControlPacket { + store.RLock() + defer store.RUnlock() + if !store.opened { + ERROR.Println(STR, "Trying to use file store, but not open") + return nil + } + filepath := fullpath(store.directory, key) + if !exists(filepath) { + return nil + } + mfile, oerr := os.Open(filepath) + chkerr(oerr) + msg, rerr := packets.ReadPacket(mfile) + chkerr(mfile.Close()) + + // Message was unreadable, return nil + if rerr != nil { + newpath := corruptpath(store.directory, key) + WARN.Println(STR, "corrupted file detected:", rerr.Error(), "archived at:", newpath) + os.Rename(filepath, newpath) + return nil + } + return msg +} + +// All will provide a list of all of the keys associated with messages +// currenly residing in the FileStore. +func (store *FileStore) All() []string { + store.RLock() + defer store.RUnlock() + return store.all() +} + +// Del will remove the persisted message associated with the provided +// key from the FileStore. +func (store *FileStore) Del(key string) { + store.Lock() + defer store.Unlock() + store.del(key) +} + +// Reset will remove all persisted messages from the FileStore. +func (store *FileStore) Reset() { + store.Lock() + defer store.Unlock() + WARN.Println(STR, "FileStore Reset") + for _, key := range store.all() { + store.del(key) + } +} + +// lockless +func (store *FileStore) all() []string { + var err error + var keys []string + var files fileInfos + + if !store.opened { + ERROR.Println(STR, "Trying to use file store, but not open") + return nil + } + + files, err = ioutil.ReadDir(store.directory) + chkerr(err) + sort.Sort(files) + for _, f := range files { + DEBUG.Println(STR, "file in All():", f.Name()) + name := f.Name() + if name[len(name)-4:len(name)] != msgExt { + DEBUG.Println(STR, "skipping file, doesn't have right extension: ", name) + continue + } + key := name[0 : len(name)-4] // remove file extension + keys = append(keys, key) + } + return keys +} + +// lockless +func (store *FileStore) del(key string) { + if !store.opened { + ERROR.Println(STR, "Trying to use file store, but not open") + return + } + DEBUG.Println(STR, "store del filepath:", store.directory) + DEBUG.Println(STR, "store delete key:", key) + filepath := fullpath(store.directory, key) + DEBUG.Println(STR, "path of deletion:", filepath) + if !exists(filepath) { + WARN.Println(STR, "store could not delete key:", key) + return + } + rerr := os.Remove(filepath) + chkerr(rerr) + DEBUG.Println(STR, "del msg:", key) + if exists(filepath) { + ERROR.Println(STR, "file not deleted:", filepath) + } +} + +func fullpath(store string, key string) string { + p := path.Join(store, key+msgExt) + return p +} + +func tmppath(store string, key string) string { + p := path.Join(store, key+tmpExt) + return p +} + +func corruptpath(store string, key string) string { + p := path.Join(store, key+corruptExt) + return p +} + +// create file called "X.[messageid].tmp" located in the store +// the contents of the file is the bytes of the message, then +// rename it to "X.[messageid].msg", overwriting any existing +// message with the same id +// X will be 'i' for inbound messages, and O for outbound messages +func write(store, key string, m packets.ControlPacket) { + temppath := tmppath(store, key) + f, err := os.Create(temppath) + chkerr(err) + werr := m.Write(f) + chkerr(werr) + cerr := f.Close() + chkerr(cerr) + rerr := os.Rename(temppath, fullpath(store, key)) + chkerr(rerr) +} + +func exists(file string) bool { + if _, err := os.Stat(file); err != nil { + if os.IsNotExist(err) { + return false + } + chkerr(err) + } + return true +} + +type fileInfos []os.FileInfo + +func (f fileInfos) Len() int { + return len(f) +} + +func (f fileInfos) Swap(i, j int) { + f[i], f[j] = f[j], f[i] +} + +func (f fileInfos) Less(i, j int) bool { + return f[i].ModTime().Before(f[j].ModTime()) +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/memstore.go b/vendor/github.com/eclipse/paho.mqtt.golang/memstore.go new file mode 100644 index 0000000..499c490 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/memstore.go @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +package mqtt + +import ( + "sync" + + "github.com/eclipse/paho.mqtt.golang/packets" +) + +// MemoryStore implements the store interface to provide a "persistence" +// mechanism wholly stored in memory. This is only useful for +// as long as the client instance exists. +type MemoryStore struct { + sync.RWMutex + messages map[string]packets.ControlPacket + opened bool +} + +// NewMemoryStore returns a pointer to a new instance of +// MemoryStore, the instance is not initialized and ready to +// use until Open() has been called on it. +func NewMemoryStore() *MemoryStore { + store := &MemoryStore{ + messages: make(map[string]packets.ControlPacket), + opened: false, + } + return store +} + +// Open initializes a MemoryStore instance. +func (store *MemoryStore) Open() { + store.Lock() + defer store.Unlock() + store.opened = true + DEBUG.Println(STR, "memorystore initialized") +} + +// Put takes a key and a pointer to a Message and stores the +// message. +func (store *MemoryStore) Put(key string, message packets.ControlPacket) { + store.Lock() + defer store.Unlock() + if !store.opened { + ERROR.Println(STR, "Trying to use memory store, but not open") + return + } + store.messages[key] = message +} + +// Get takes a key and looks in the store for a matching Message +// returning either the Message pointer or nil. +func (store *MemoryStore) Get(key string) packets.ControlPacket { + store.RLock() + defer store.RUnlock() + if !store.opened { + ERROR.Println(STR, "Trying to use memory store, but not open") + return nil + } + mid := mIDFromKey(key) + m := store.messages[key] + if m == nil { + CRITICAL.Println(STR, "memorystore get: message", mid, "not found") + } else { + DEBUG.Println(STR, "memorystore get: message", mid, "found") + } + return m +} + +// All returns a slice of strings containing all the keys currently +// in the MemoryStore. +func (store *MemoryStore) All() []string { + store.RLock() + defer store.RUnlock() + if !store.opened { + ERROR.Println(STR, "Trying to use memory store, but not open") + return nil + } + keys := []string{} + for k := range store.messages { + keys = append(keys, k) + } + return keys +} + +// Del takes a key, searches the MemoryStore and if the key is found +// deletes the Message pointer associated with it. +func (store *MemoryStore) Del(key string) { + store.Lock() + defer store.Unlock() + if !store.opened { + ERROR.Println(STR, "Trying to use memory store, but not open") + return + } + mid := mIDFromKey(key) + m := store.messages[key] + if m == nil { + WARN.Println(STR, "memorystore del: message", mid, "not found") + } else { + delete(store.messages, key) + DEBUG.Println(STR, "memorystore del: message", mid, "was deleted") + } +} + +// Close will disallow modifications to the state of the store. +func (store *MemoryStore) Close() { + store.Lock() + defer store.Unlock() + if !store.opened { + ERROR.Println(STR, "Trying to close memory store, but not open") + return + } + store.opened = false + DEBUG.Println(STR, "memorystore closed") +} + +// Reset eliminates all persisted message data in the store. +func (store *MemoryStore) Reset() { + store.Lock() + defer store.Unlock() + if !store.opened { + ERROR.Println(STR, "Trying to reset memory store, but not open") + } + store.messages = make(map[string]packets.ControlPacket) + WARN.Println(STR, "memorystore wiped") +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/message.go b/vendor/github.com/eclipse/paho.mqtt.golang/message.go new file mode 100644 index 0000000..903e5dc --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/message.go @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +package mqtt + +import ( + "net/url" + + "github.com/eclipse/paho.mqtt.golang/packets" + "sync" +) + +// Message defines the externals that a message implementation must support +// these are received messages that are passed to the callbacks, not internal +// messages +type Message interface { + Duplicate() bool + Qos() byte + Retained() bool + Topic() string + MessageID() uint16 + Payload() []byte + Ack() +} + +type message struct { + duplicate bool + qos byte + retained bool + topic string + messageID uint16 + payload []byte + once sync.Once + ack func() +} + +func (m *message) Duplicate() bool { + return m.duplicate +} + +func (m *message) Qos() byte { + return m.qos +} + +func (m *message) Retained() bool { + return m.retained +} + +func (m *message) Topic() string { + return m.topic +} + +func (m *message) MessageID() uint16 { + return m.messageID +} + +func (m *message) Payload() []byte { + return m.payload +} + +func (m *message) Ack() { + m.once.Do(m.ack) +} + +func messageFromPublish(p *packets.PublishPacket, ack func()) Message { + return &message{ + duplicate: p.Dup, + qos: p.Qos, + retained: p.Retain, + topic: p.TopicName, + messageID: p.MessageID, + payload: p.Payload, + ack: ack, + } +} + +func newConnectMsgFromOptions(options *ClientOptions, broker *url.URL) *packets.ConnectPacket { + m := packets.NewControlPacket(packets.Connect).(*packets.ConnectPacket) + + m.CleanSession = options.CleanSession + m.WillFlag = options.WillEnabled + m.WillRetain = options.WillRetained + m.ClientIdentifier = options.ClientID + + if options.WillEnabled { + m.WillQos = options.WillQos + m.WillTopic = options.WillTopic + m.WillMessage = options.WillPayload + } + + username := options.Username + password := options.Password + if broker.User != nil { + username = broker.User.Username() + if pwd, ok := broker.User.Password(); ok { + password = pwd + } + } + if options.CredentialsProvider != nil { + username, password = options.CredentialsProvider() + } + + if username != "" { + m.UsernameFlag = true + m.Username = username + //mustn't have password without user as well + if password != "" { + m.PasswordFlag = true + m.Password = []byte(password) + } + } + + m.Keepalive = uint16(options.KeepAlive) + + return m +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/messageids.go b/vendor/github.com/eclipse/paho.mqtt.golang/messageids.go new file mode 100644 index 0000000..9a5fa9f --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/messageids.go @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +package mqtt + +import ( + "fmt" + "sync" + "time" +) + +// MId is 16 bit message id as specified by the MQTT spec. +// In general, these values should not be depended upon by +// the client application. +type MId uint16 + +type messageIds struct { + sync.RWMutex + index map[uint16]tokenCompletor +} + +const ( + midMin uint16 = 1 + midMax uint16 = 65535 +) + +func (mids *messageIds) cleanUp() { + mids.Lock() + for _, token := range mids.index { + switch token.(type) { + case *PublishToken: + token.setError(fmt.Errorf("Connection lost before Publish completed")) + case *SubscribeToken: + token.setError(fmt.Errorf("Connection lost before Subscribe completed")) + case *UnsubscribeToken: + token.setError(fmt.Errorf("Connection lost before Unsubscribe completed")) + case nil: + continue + } + token.flowComplete() + } + mids.index = make(map[uint16]tokenCompletor) + mids.Unlock() + DEBUG.Println(MID, "cleaned up") +} + +func (mids *messageIds) freeID(id uint16) { + mids.Lock() + delete(mids.index, id) + mids.Unlock() +} + +func (mids *messageIds) claimID(token tokenCompletor, id uint16) { + mids.Lock() + defer mids.Unlock() + if _, ok := mids.index[id]; !ok { + mids.index[id] = token + } else { + old := mids.index[id] + old.flowComplete() + mids.index[id] = token + } +} + +func (mids *messageIds) getID(t tokenCompletor) uint16 { + mids.Lock() + defer mids.Unlock() + for i := midMin; i < midMax; i++ { + if _, ok := mids.index[i]; !ok { + mids.index[i] = t + return i + } + } + return 0 +} + +func (mids *messageIds) getToken(id uint16) tokenCompletor { + mids.RLock() + defer mids.RUnlock() + if token, ok := mids.index[id]; ok { + return token + } + return &DummyToken{id: id} +} + +type DummyToken struct { + id uint16 +} + +func (d *DummyToken) Wait() bool { + return true +} + +func (d *DummyToken) WaitTimeout(t time.Duration) bool { + return true +} + +func (d *DummyToken) flowComplete() { + ERROR.Printf("A lookup for token %d returned nil\n", d.id) +} + +func (d *DummyToken) Error() error { + return nil +} + +func (d *DummyToken) setError(e error) {} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/net.go b/vendor/github.com/eclipse/paho.mqtt.golang/net.go new file mode 100644 index 0000000..3e6366b --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/net.go @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +package mqtt + +import ( + "crypto/tls" + "errors" + "fmt" + "net" + "net/http" + "net/url" + "os" + "reflect" + "sync/atomic" + "time" + + "github.com/eclipse/paho.mqtt.golang/packets" + "golang.org/x/net/proxy" + "golang.org/x/net/websocket" +) + +func signalError(c chan<- error, err error) { + select { + case c <- err: + default: + } +} + +func openConnection(uri *url.URL, tlsc *tls.Config, timeout time.Duration, headers http.Header) (net.Conn, error) { + switch uri.Scheme { + case "ws": + config, _ := websocket.NewConfig(uri.String(), fmt.Sprintf("http://%s", uri.Host)) + config.Protocol = []string{"mqtt"} + config.Header = headers + config.Dialer = &net.Dialer{Timeout: timeout} + conn, err := websocket.DialConfig(config) + if err != nil { + return nil, err + } + conn.PayloadType = websocket.BinaryFrame + return conn, err + case "wss": + config, _ := websocket.NewConfig(uri.String(), fmt.Sprintf("https://%s", uri.Host)) + config.Protocol = []string{"mqtt"} + config.TlsConfig = tlsc + config.Header = headers + config.Dialer = &net.Dialer{Timeout: timeout} + conn, err := websocket.DialConfig(config) + if err != nil { + return nil, err + } + conn.PayloadType = websocket.BinaryFrame + return conn, err + case "tcp": + allProxy := os.Getenv("all_proxy") + if len(allProxy) == 0 { + conn, err := net.DialTimeout("tcp", uri.Host, timeout) + if err != nil { + return nil, err + } + return conn, nil + } + proxyDialer := proxy.FromEnvironment() + + conn, err := proxyDialer.Dial("tcp", uri.Host) + if err != nil { + return nil, err + } + return conn, nil + case "unix": + conn, err := net.DialTimeout("unix", uri.Host, timeout) + if err != nil { + return nil, err + } + return conn, nil + case "ssl": + fallthrough + case "tls": + fallthrough + case "tcps": + allProxy := os.Getenv("all_proxy") + if len(allProxy) == 0 { + conn, err := tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", uri.Host, tlsc) + if err != nil { + return nil, err + } + return conn, nil + } + proxyDialer := proxy.FromEnvironment() + + conn, err := proxyDialer.Dial("tcp", uri.Host) + if err != nil { + return nil, err + } + + tlsConn := tls.Client(conn, tlsc) + + err = tlsConn.Handshake() + if err != nil { + conn.Close() + return nil, err + } + + return tlsConn, nil + } + return nil, errors.New("Unknown protocol") +} + +// actually read incoming messages off the wire +// send Message object into ibound channel +func incoming(c *client) { + var err error + var cp packets.ControlPacket + + defer c.workers.Done() + + DEBUG.Println(NET, "incoming started") + + for { + if cp, err = packets.ReadPacket(c.conn); err != nil { + break + } + DEBUG.Println(NET, "Received Message") + select { + case c.ibound <- cp: + // Notify keepalive logic that we recently received a packet + if c.options.KeepAlive != 0 { + c.lastReceived.Store(time.Now()) + } + case <-c.stop: + // This avoids a deadlock should a message arrive while shutting down. + // In that case the "reader" of c.ibound might already be gone + WARN.Println(NET, "incoming dropped a received message during shutdown") + break + } + } + // We received an error on read. + // If disconnect is in progress, swallow error and return + select { + case <-c.stop: + DEBUG.Println(NET, "incoming stopped") + return + // Not trying to disconnect, send the error to the errors channel + default: + ERROR.Println(NET, "incoming stopped with error", err) + signalError(c.errors, err) + return + } +} + +// receive a Message object on obound, and then +// actually send outgoing message to the wire +func outgoing(c *client) { + defer c.workers.Done() + DEBUG.Println(NET, "outgoing started") + + for { + DEBUG.Println(NET, "outgoing waiting for an outbound message") + select { + case <-c.stop: + DEBUG.Println(NET, "outgoing stopped") + return + case pub := <-c.obound: + msg := pub.p.(*packets.PublishPacket) + + if c.options.WriteTimeout > 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.options.WriteTimeout)) + } + + if err := msg.Write(c.conn); err != nil { + ERROR.Println(NET, "outgoing stopped with error", err) + pub.t.setError(err) + signalError(c.errors, err) + return + } + + if c.options.WriteTimeout > 0 { + // If we successfully wrote, we don't want the timeout to happen during an idle period + // so we reset it to infinite. + c.conn.SetWriteDeadline(time.Time{}) + } + + if msg.Qos == 0 { + pub.t.flowComplete() + } + DEBUG.Println(NET, "obound wrote msg, id:", msg.MessageID) + case msg := <-c.oboundP: + switch msg.p.(type) { + case *packets.SubscribePacket: + msg.p.(*packets.SubscribePacket).MessageID = c.getID(msg.t) + case *packets.UnsubscribePacket: + msg.p.(*packets.UnsubscribePacket).MessageID = c.getID(msg.t) + } + DEBUG.Println(NET, "obound priority msg to write, type", reflect.TypeOf(msg.p)) + if err := msg.p.Write(c.conn); err != nil { + ERROR.Println(NET, "outgoing stopped with error", err) + if msg.t != nil { + msg.t.setError(err) + } + signalError(c.errors, err) + return + } + switch msg.p.(type) { + case *packets.DisconnectPacket: + msg.t.(*DisconnectToken).flowComplete() + DEBUG.Println(NET, "outbound wrote disconnect, stopping") + return + } + } + // Reset ping timer after sending control packet. + if c.options.KeepAlive != 0 { + c.lastSent.Store(time.Now()) + } + } +} + +// receive Message objects on ibound +// store messages if necessary +// send replies on obound +// delete messages from store if necessary +func alllogic(c *client) { + defer c.workers.Done() + DEBUG.Println(NET, "logic started") + + for { + DEBUG.Println(NET, "logic waiting for msg on ibound") + + select { + case msg := <-c.ibound: + DEBUG.Println(NET, "logic got msg on ibound") + persistInbound(c.persist, msg) + switch m := msg.(type) { + case *packets.PingrespPacket: + DEBUG.Println(NET, "received pingresp") + atomic.StoreInt32(&c.pingOutstanding, 0) + case *packets.SubackPacket: + DEBUG.Println(NET, "received suback, id:", m.MessageID) + token := c.getToken(m.MessageID) + switch t := token.(type) { + case *SubscribeToken: + DEBUG.Println(NET, "granted qoss", m.ReturnCodes) + for i, qos := range m.ReturnCodes { + t.subResult[t.subs[i]] = qos + } + } + token.flowComplete() + c.freeID(m.MessageID) + case *packets.UnsubackPacket: + DEBUG.Println(NET, "received unsuback, id:", m.MessageID) + c.getToken(m.MessageID).flowComplete() + c.freeID(m.MessageID) + case *packets.PublishPacket: + DEBUG.Println(NET, "received publish, msgId:", m.MessageID) + DEBUG.Println(NET, "putting msg on onPubChan") + switch m.Qos { + case 2: + c.incomingPubChan <- m + DEBUG.Println(NET, "done putting msg on incomingPubChan") + case 1: + c.incomingPubChan <- m + DEBUG.Println(NET, "done putting msg on incomingPubChan") + case 0: + select { + case c.incomingPubChan <- m: + case <-c.stop: + } + DEBUG.Println(NET, "done putting msg on incomingPubChan") + } + case *packets.PubackPacket: + DEBUG.Println(NET, "received puback, id:", m.MessageID) + // c.receipts.get(msg.MsgId()) <- Receipt{} + // c.receipts.end(msg.MsgId()) + c.getToken(m.MessageID).flowComplete() + c.freeID(m.MessageID) + case *packets.PubrecPacket: + DEBUG.Println(NET, "received pubrec, id:", m.MessageID) + prel := packets.NewControlPacket(packets.Pubrel).(*packets.PubrelPacket) + prel.MessageID = m.MessageID + select { + case c.oboundP <- &PacketAndToken{p: prel, t: nil}: + case <-c.stop: + } + case *packets.PubrelPacket: + DEBUG.Println(NET, "received pubrel, id:", m.MessageID) + pc := packets.NewControlPacket(packets.Pubcomp).(*packets.PubcompPacket) + pc.MessageID = m.MessageID + persistOutbound(c.persist, pc) + select { + case c.oboundP <- &PacketAndToken{p: pc, t: nil}: + case <-c.stop: + } + case *packets.PubcompPacket: + DEBUG.Println(NET, "received pubcomp, id:", m.MessageID) + c.getToken(m.MessageID).flowComplete() + c.freeID(m.MessageID) + } + case <-c.stop: + WARN.Println(NET, "logic stopped") + return + } + } +} + +func (c *client) ackFunc(packet *packets.PublishPacket) func() { + return func() { + switch packet.Qos { + case 2: + pr := packets.NewControlPacket(packets.Pubrec).(*packets.PubrecPacket) + pr.MessageID = packet.MessageID + DEBUG.Println(NET, "putting pubrec msg on obound") + select { + case c.oboundP <- &PacketAndToken{p: pr, t: nil}: + case <-c.stop: + } + DEBUG.Println(NET, "done putting pubrec msg on obound") + case 1: + pa := packets.NewControlPacket(packets.Puback).(*packets.PubackPacket) + pa.MessageID = packet.MessageID + DEBUG.Println(NET, "putting puback msg on obound") + persistOutbound(c.persist, pa) + select { + case c.oboundP <- &PacketAndToken{p: pa, t: nil}: + case <-c.stop: + } + DEBUG.Println(NET, "done putting puback msg on obound") + case 0: + // do nothing, since there is no need to send an ack packet back + } + } +} + +func errorWatch(c *client) { + defer c.workers.Done() + select { + case <-c.stop: + WARN.Println(NET, "errorWatch stopped") + return + case err := <-c.errors: + ERROR.Println(NET, "error triggered, stopping") + go c.internalConnLost(err) + return + } +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/notice.html b/vendor/github.com/eclipse/paho.mqtt.golang/notice.html new file mode 100644 index 0000000..f19c483 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/notice.html @@ -0,0 +1,108 @@ + + + + + +Eclipse Foundation Software User Agreement + + + +

Eclipse Foundation Software User Agreement

+

February 1, 2011

+ +

Usage Of Content

+ +

THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS + (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR THE TERMS AND + CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR USE + OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR + NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS AND + CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE CONTENT.

+ +

Applicable Licenses

+ +

Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms and conditions of the Eclipse Public License Version 1.0 + ("EPL"). A copy of the EPL is provided with this Content and is also available at http://www.eclipse.org/legal/epl-v10.html. + For purposes of the EPL, "Program" will mean the Content.

+ +

Content includes, but is not limited to, source code, object code, documentation and other files maintained in the Eclipse Foundation source code + repository ("Repository") in software modules ("Modules") and made available as downloadable archives ("Downloads").

+ + + +

The terms and conditions governing Plug-ins and Fragments should be contained in files named "about.html" ("Abouts"). The terms and conditions governing Features and +Included Features should be contained in files named "license.html" ("Feature Licenses"). Abouts and Feature Licenses may be located in any directory of a Download or Module +including, but not limited to the following locations:

+ + + +

Note: if a Feature made available by the Eclipse Foundation is installed using the Provisioning Technology (as defined below), you must agree to a license ("Feature Update License") during the +installation process. If the Feature contains Included Features, the Feature Update License should either provide you with the terms and conditions governing the Included Features or +inform you where you can locate them. Feature Update Licenses may be found in the "license" property of files named "feature.properties" found within a Feature. +Such Abouts, Feature Licenses, and Feature Update Licenses contain the terms and conditions (or references to such terms and conditions) that govern your use of the associated Content in +that directory.

+ +

THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND CONDITIONS. SOME OF THESE +OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):

+ + + +

IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, Feature License, or Feature Update License is provided, please +contact the Eclipse Foundation to determine what terms and conditions govern that particular Content.

+ + +

Use of Provisioning Technology

+ +

The Eclipse Foundation makes available provisioning software, examples of which include, but are not limited to, p2 and the Eclipse + Update Manager ("Provisioning Technology") for the purpose of allowing users to install software, documentation, information and/or + other materials (collectively "Installable Software"). This capability is provided with the intent of allowing such users to + install, extend and update Eclipse-based products. Information about packaging Installable Software is available at http://eclipse.org/equinox/p2/repository_packaging.html + ("Specification").

+ +

You may use Provisioning Technology to allow other parties to install Installable Software. You shall be responsible for enabling the + applicable license agreements relating to the Installable Software to be presented to, and accepted by, the users of the Provisioning Technology + in accordance with the Specification. By using Provisioning Technology in such a manner and making it available in accordance with the + Specification, you further acknowledge your agreement to, and the acquisition of all necessary rights to permit the following:

+ +
    +
  1. A series of actions may occur ("Provisioning Process") in which a user may execute the Provisioning Technology + on a machine ("Target Machine") with the intent of installing, extending or updating the functionality of an Eclipse-based + product.
  2. +
  3. During the Provisioning Process, the Provisioning Technology may cause third party Installable Software or a portion thereof to be + accessed and copied to the Target Machine.
  4. +
  5. Pursuant to the Specification, you will provide to the user the terms and conditions that govern the use of the Installable + Software ("Installable Software Agreement") and such Installable Software Agreement shall be accessed from the Target + Machine in accordance with the Specification. Such Installable Software Agreement must inform the user of the terms and conditions that govern + the Installable Software and must solicit acceptance by the end user in the manner prescribed in such Installable Software Agreement. Upon such + indication of agreement by the user, the provisioning Technology will complete installation of the Installable Software.
  6. +
+ +

Cryptography

+ +

Content may contain encryption software. The country in which you are currently may have restrictions on the import, possession, and use, and/or re-export to + another country, of encryption software. BEFORE using any encryption software, please check the country's laws, regulations and policies concerning the import, + possession, or use, and re-export of encryption software, to see if this is permitted.

+ +

Java and all Java-based trademarks are trademarks of Oracle Corporation in the United States, other countries, or both.

+ + diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/oops.go b/vendor/github.com/eclipse/paho.mqtt.golang/oops.go new file mode 100644 index 0000000..39630d7 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/oops.go @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +package mqtt + +func chkerr(e error) { + if e != nil { + panic(e) + } +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/options.go b/vendor/github.com/eclipse/paho.mqtt.golang/options.go new file mode 100644 index 0000000..e96e9ed --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/options.go @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +// Portions copyright © 2018 TIBCO Software Inc. + +package mqtt + +import ( + "crypto/tls" + "net/http" + "net/url" + "strings" + "time" +) + +// CredentialsProvider allows the username and password to be updated +// before reconnecting. It should return the current username and password. +type CredentialsProvider func() (username string, password string) + +// MessageHandler is a callback type which can be set to be +// executed upon the arrival of messages published to topics +// to which the client is subscribed. +type MessageHandler func(Client, Message) + +// ConnectionLostHandler is a callback type which can be set to be +// executed upon an unintended disconnection from the MQTT broker. +// Disconnects caused by calling Disconnect or ForceDisconnect will +// not cause an OnConnectionLost callback to execute. +type ConnectionLostHandler func(Client, error) + +// OnConnectHandler is a callback that is called when the client +// state changes from unconnected/disconnected to connected. Both +// at initial connection and on reconnection +type OnConnectHandler func(Client) + +// ClientOptions contains configurable options for an Client. +type ClientOptions struct { + Servers []*url.URL + ClientID string + Username string + Password string + CredentialsProvider CredentialsProvider + CleanSession bool + Order bool + WillEnabled bool + WillTopic string + WillPayload []byte + WillQos byte + WillRetained bool + ProtocolVersion uint + protocolVersionExplicit bool + TLSConfig *tls.Config + KeepAlive int64 + PingTimeout time.Duration + ConnectTimeout time.Duration + MaxReconnectInterval time.Duration + AutoReconnect bool + Store Store + DefaultPublishHandler MessageHandler + OnConnect OnConnectHandler + OnConnectionLost ConnectionLostHandler + WriteTimeout time.Duration + MessageChannelDepth uint + ResumeSubs bool + HTTPHeaders http.Header +} + +// NewClientOptions will create a new ClientClientOptions type with some +// default values. +// Port: 1883 +// CleanSession: True +// Order: True +// KeepAlive: 30 (seconds) +// ConnectTimeout: 30 (seconds) +// MaxReconnectInterval 10 (minutes) +// AutoReconnect: True +func NewClientOptions() *ClientOptions { + o := &ClientOptions{ + Servers: nil, + ClientID: "", + Username: "", + Password: "", + CleanSession: true, + Order: true, + WillEnabled: false, + WillTopic: "", + WillPayload: nil, + WillQos: 0, + WillRetained: false, + ProtocolVersion: 0, + protocolVersionExplicit: false, + KeepAlive: 30, + PingTimeout: 10 * time.Second, + ConnectTimeout: 30 * time.Second, + MaxReconnectInterval: 10 * time.Minute, + AutoReconnect: true, + Store: nil, + OnConnect: nil, + OnConnectionLost: DefaultConnectionLostHandler, + WriteTimeout: 0, // 0 represents timeout disabled + MessageChannelDepth: 100, + ResumeSubs: false, + HTTPHeaders: make(map[string][]string), + } + return o +} + +// AddBroker adds a broker URI to the list of brokers to be used. The format should be +// scheme://host:port +// Where "scheme" is one of "tcp", "ssl", or "ws", "host" is the ip-address (or hostname) +// and "port" is the port on which the broker is accepting connections. +// +// Default values for hostname is "127.0.0.1", for schema is "tcp://". +// +// An example broker URI would look like: tcp://foobar.com:1883 +func (o *ClientOptions) AddBroker(server string) *ClientOptions { + if len(server) > 0 && server[0] == ':' { + server = "127.0.0.1" + server + } + if !strings.Contains(server, "://") { + server = "tcp://" + server + } + brokerURI, err := url.Parse(server) + if err != nil { + ERROR.Println(CLI, "Failed to parse %q broker address: %s", server, err) + return o + } + o.Servers = append(o.Servers, brokerURI) + return o +} + +// SetResumeSubs will enable resuming of stored (un)subscribe messages when connecting +// but not reconnecting if CleanSession is false. Otherwise these messages are discarded. +func (o *ClientOptions) SetResumeSubs(resume bool) *ClientOptions { + o.ResumeSubs = resume + return o +} + +// SetClientID will set the client id to be used by this client when +// connecting to the MQTT broker. According to the MQTT v3.1 specification, +// a client id mus be no longer than 23 characters. +func (o *ClientOptions) SetClientID(id string) *ClientOptions { + o.ClientID = id + return o +} + +// SetUsername will set the username to be used by this client when connecting +// to the MQTT broker. Note: without the use of SSL/TLS, this information will +// be sent in plaintext accross the wire. +func (o *ClientOptions) SetUsername(u string) *ClientOptions { + o.Username = u + return o +} + +// SetPassword will set the password to be used by this client when connecting +// to the MQTT broker. Note: without the use of SSL/TLS, this information will +// be sent in plaintext accross the wire. +func (o *ClientOptions) SetPassword(p string) *ClientOptions { + o.Password = p + return o +} + +// SetCredentialsProvider will set a method to be called by this client when +// connecting to the MQTT broker that provide the current username and password. +// Note: without the use of SSL/TLS, this information will be sent +// in plaintext accross the wire. +func (o *ClientOptions) SetCredentialsProvider(p CredentialsProvider) *ClientOptions { + o.CredentialsProvider = p + return o +} + +// SetCleanSession will set the "clean session" flag in the connect message +// when this client connects to an MQTT broker. By setting this flag, you are +// indicating that no messages saved by the broker for this client should be +// delivered. Any messages that were going to be sent by this client before +// diconnecting previously but didn't will not be sent upon connecting to the +// broker. +func (o *ClientOptions) SetCleanSession(clean bool) *ClientOptions { + o.CleanSession = clean + return o +} + +// SetOrderMatters will set the message routing to guarantee order within +// each QoS level. By default, this value is true. If set to false, +// this flag indicates that messages can be delivered asynchronously +// from the client to the application and possibly arrive out of order. +func (o *ClientOptions) SetOrderMatters(order bool) *ClientOptions { + o.Order = order + return o +} + +// SetTLSConfig will set an SSL/TLS configuration to be used when connecting +// to an MQTT broker. Please read the official Go documentation for more +// information. +func (o *ClientOptions) SetTLSConfig(t *tls.Config) *ClientOptions { + o.TLSConfig = t + return o +} + +// SetStore will set the implementation of the Store interface +// used to provide message persistence in cases where QoS levels +// QoS_ONE or QoS_TWO are used. If no store is provided, then the +// client will use MemoryStore by default. +func (o *ClientOptions) SetStore(s Store) *ClientOptions { + o.Store = s + return o +} + +// SetKeepAlive will set the amount of time (in seconds) that the client +// should wait before sending a PING request to the broker. This will +// allow the client to know that a connection has not been lost with the +// server. +func (o *ClientOptions) SetKeepAlive(k time.Duration) *ClientOptions { + o.KeepAlive = int64(k / time.Second) + return o +} + +// SetPingTimeout will set the amount of time (in seconds) that the client +// will wait after sending a PING request to the broker, before deciding +// that the connection has been lost. Default is 10 seconds. +func (o *ClientOptions) SetPingTimeout(k time.Duration) *ClientOptions { + o.PingTimeout = k + return o +} + +// SetProtocolVersion sets the MQTT version to be used to connect to the +// broker. Legitimate values are currently 3 - MQTT 3.1 or 4 - MQTT 3.1.1 +func (o *ClientOptions) SetProtocolVersion(pv uint) *ClientOptions { + if (pv >= 3 && pv <= 4) || (pv > 0x80) { + o.ProtocolVersion = pv + o.protocolVersionExplicit = true + } + return o +} + +// UnsetWill will cause any set will message to be disregarded. +func (o *ClientOptions) UnsetWill() *ClientOptions { + o.WillEnabled = false + return o +} + +// SetWill accepts a string will message to be set. When the client connects, +// it will give this will message to the broker, which will then publish the +// provided payload (the will) to any clients that are subscribed to the provided +// topic. +func (o *ClientOptions) SetWill(topic string, payload string, qos byte, retained bool) *ClientOptions { + o.SetBinaryWill(topic, []byte(payload), qos, retained) + return o +} + +// SetBinaryWill accepts a []byte will message to be set. When the client connects, +// it will give this will message to the broker, which will then publish the +// provided payload (the will) to any clients that are subscribed to the provided +// topic. +func (o *ClientOptions) SetBinaryWill(topic string, payload []byte, qos byte, retained bool) *ClientOptions { + o.WillEnabled = true + o.WillTopic = topic + o.WillPayload = payload + o.WillQos = qos + o.WillRetained = retained + return o +} + +// SetDefaultPublishHandler sets the MessageHandler that will be called when a message +// is received that does not match any known subscriptions. +func (o *ClientOptions) SetDefaultPublishHandler(defaultHandler MessageHandler) *ClientOptions { + o.DefaultPublishHandler = defaultHandler + return o +} + +// SetOnConnectHandler sets the function to be called when the client is connected. Both +// at initial connection time and upon automatic reconnect. +func (o *ClientOptions) SetOnConnectHandler(onConn OnConnectHandler) *ClientOptions { + o.OnConnect = onConn + return o +} + +// SetConnectionLostHandler will set the OnConnectionLost callback to be executed +// in the case where the client unexpectedly loses connection with the MQTT broker. +func (o *ClientOptions) SetConnectionLostHandler(onLost ConnectionLostHandler) *ClientOptions { + o.OnConnectionLost = onLost + return o +} + +// SetWriteTimeout puts a limit on how long a mqtt publish should block until it unblocks with a +// timeout error. A duration of 0 never times out. Default 30 seconds +func (o *ClientOptions) SetWriteTimeout(t time.Duration) *ClientOptions { + o.WriteTimeout = t + return o +} + +// SetConnectTimeout limits how long the client will wait when trying to open a connection +// to an MQTT server before timeing out and erroring the attempt. A duration of 0 never times out. +// Default 30 seconds. Currently only operational on TCP/TLS connections. +func (o *ClientOptions) SetConnectTimeout(t time.Duration) *ClientOptions { + o.ConnectTimeout = t + return o +} + +// SetMaxReconnectInterval sets the maximum time that will be waited between reconnection attempts +// when connection is lost +func (o *ClientOptions) SetMaxReconnectInterval(t time.Duration) *ClientOptions { + o.MaxReconnectInterval = t + return o +} + +// SetAutoReconnect sets whether the automatic reconnection logic should be used +// when the connection is lost, even if disabled the ConnectionLostHandler is still +// called +func (o *ClientOptions) SetAutoReconnect(a bool) *ClientOptions { + o.AutoReconnect = a + return o +} + +// SetMessageChannelDepth sets the size of the internal queue that holds messages while the +// client is temporairily offline, allowing the application to publish when the client is +// reconnecting. This setting is only valid if AutoReconnect is set to true, it is otherwise +// ignored. +func (o *ClientOptions) SetMessageChannelDepth(s uint) *ClientOptions { + o.MessageChannelDepth = s + return o +} + +// SetHTTPHeaders sets the additional HTTP headers that will be sent in the WebSocket +// opening handshake. +func (o *ClientOptions) SetHTTPHeaders(h http.Header) *ClientOptions { + o.HTTPHeaders = h + return o +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/options_reader.go b/vendor/github.com/eclipse/paho.mqtt.golang/options_reader.go new file mode 100644 index 0000000..60144b9 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/options_reader.go @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +package mqtt + +import ( + "crypto/tls" + "net/http" + "net/url" + "time" +) + +// ClientOptionsReader provides an interface for reading ClientOptions after the client has been initialized. +type ClientOptionsReader struct { + options *ClientOptions +} + +//Servers returns a slice of the servers defined in the clientoptions +func (r *ClientOptionsReader) Servers() []*url.URL { + s := make([]*url.URL, len(r.options.Servers)) + + for i, u := range r.options.Servers { + nu := *u + s[i] = &nu + } + + return s +} + +//ResumeSubs returns true if resuming stored (un)sub is enabled +func (r *ClientOptionsReader) ResumeSubs() bool { + s := r.options.ResumeSubs + return s +} + +//ClientID returns the set client id +func (r *ClientOptionsReader) ClientID() string { + s := r.options.ClientID + return s +} + +//Username returns the set username +func (r *ClientOptionsReader) Username() string { + s := r.options.Username + return s +} + +//Password returns the set password +func (r *ClientOptionsReader) Password() string { + s := r.options.Password + return s +} + +//CleanSession returns whether Cleansession is set +func (r *ClientOptionsReader) CleanSession() bool { + s := r.options.CleanSession + return s +} + +func (r *ClientOptionsReader) Order() bool { + s := r.options.Order + return s +} + +func (r *ClientOptionsReader) WillEnabled() bool { + s := r.options.WillEnabled + return s +} + +func (r *ClientOptionsReader) WillTopic() string { + s := r.options.WillTopic + return s +} + +func (r *ClientOptionsReader) WillPayload() []byte { + s := r.options.WillPayload + return s +} + +func (r *ClientOptionsReader) WillQos() byte { + s := r.options.WillQos + return s +} + +func (r *ClientOptionsReader) WillRetained() bool { + s := r.options.WillRetained + return s +} + +func (r *ClientOptionsReader) ProtocolVersion() uint { + s := r.options.ProtocolVersion + return s +} + +func (r *ClientOptionsReader) TLSConfig() *tls.Config { + s := r.options.TLSConfig + return s +} + +func (r *ClientOptionsReader) KeepAlive() time.Duration { + s := time.Duration(r.options.KeepAlive * int64(time.Second)) + return s +} + +func (r *ClientOptionsReader) PingTimeout() time.Duration { + s := r.options.PingTimeout + return s +} + +func (r *ClientOptionsReader) ConnectTimeout() time.Duration { + s := r.options.ConnectTimeout + return s +} + +func (r *ClientOptionsReader) MaxReconnectInterval() time.Duration { + s := r.options.MaxReconnectInterval + return s +} + +func (r *ClientOptionsReader) AutoReconnect() bool { + s := r.options.AutoReconnect + return s +} + +func (r *ClientOptionsReader) WriteTimeout() time.Duration { + s := r.options.WriteTimeout + return s +} + +func (r *ClientOptionsReader) MessageChannelDepth() uint { + s := r.options.MessageChannelDepth + return s +} + +func (r *ClientOptionsReader) HTTPHeaders() http.Header { + h := r.options.HTTPHeaders + return h +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/connack.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/connack.go new file mode 100644 index 0000000..25cf30f --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/connack.go @@ -0,0 +1,55 @@ +package packets + +import ( + "bytes" + "fmt" + "io" +) + +//ConnackPacket is an internal representation of the fields of the +//Connack MQTT packet +type ConnackPacket struct { + FixedHeader + SessionPresent bool + ReturnCode byte +} + +func (ca *ConnackPacket) String() string { + str := fmt.Sprintf("%s", ca.FixedHeader) + str += " " + str += fmt.Sprintf("sessionpresent: %t returncode: %d", ca.SessionPresent, ca.ReturnCode) + return str +} + +func (ca *ConnackPacket) Write(w io.Writer) error { + var body bytes.Buffer + var err error + + body.WriteByte(boolToByte(ca.SessionPresent)) + body.WriteByte(ca.ReturnCode) + ca.FixedHeader.RemainingLength = 2 + packet := ca.FixedHeader.pack() + packet.Write(body.Bytes()) + _, err = packet.WriteTo(w) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (ca *ConnackPacket) Unpack(b io.Reader) error { + flags, err := decodeByte(b) + if err != nil { + return err + } + ca.SessionPresent = 1&flags > 0 + ca.ReturnCode, err = decodeByte(b) + + return err +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (ca *ConnackPacket) Details() Details { + return Details{Qos: 0, MessageID: 0} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/connect.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/connect.go new file mode 100644 index 0000000..cb03ebc --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/connect.go @@ -0,0 +1,154 @@ +package packets + +import ( + "bytes" + "fmt" + "io" +) + +//ConnectPacket is an internal representation of the fields of the +//Connect MQTT packet +type ConnectPacket struct { + FixedHeader + ProtocolName string + ProtocolVersion byte + CleanSession bool + WillFlag bool + WillQos byte + WillRetain bool + UsernameFlag bool + PasswordFlag bool + ReservedBit byte + Keepalive uint16 + + ClientIdentifier string + WillTopic string + WillMessage []byte + Username string + Password []byte +} + +func (c *ConnectPacket) String() string { + str := fmt.Sprintf("%s", c.FixedHeader) + str += " " + str += fmt.Sprintf("protocolversion: %d protocolname: %s cleansession: %t willflag: %t WillQos: %d WillRetain: %t Usernameflag: %t Passwordflag: %t keepalive: %d clientId: %s willtopic: %s willmessage: %s Username: %s Password: %s", c.ProtocolVersion, c.ProtocolName, c.CleanSession, c.WillFlag, c.WillQos, c.WillRetain, c.UsernameFlag, c.PasswordFlag, c.Keepalive, c.ClientIdentifier, c.WillTopic, c.WillMessage, c.Username, c.Password) + return str +} + +func (c *ConnectPacket) Write(w io.Writer) error { + var body bytes.Buffer + var err error + + body.Write(encodeString(c.ProtocolName)) + body.WriteByte(c.ProtocolVersion) + body.WriteByte(boolToByte(c.CleanSession)<<1 | boolToByte(c.WillFlag)<<2 | c.WillQos<<3 | boolToByte(c.WillRetain)<<5 | boolToByte(c.PasswordFlag)<<6 | boolToByte(c.UsernameFlag)<<7) + body.Write(encodeUint16(c.Keepalive)) + body.Write(encodeString(c.ClientIdentifier)) + if c.WillFlag { + body.Write(encodeString(c.WillTopic)) + body.Write(encodeBytes(c.WillMessage)) + } + if c.UsernameFlag { + body.Write(encodeString(c.Username)) + } + if c.PasswordFlag { + body.Write(encodeBytes(c.Password)) + } + c.FixedHeader.RemainingLength = body.Len() + packet := c.FixedHeader.pack() + packet.Write(body.Bytes()) + _, err = packet.WriteTo(w) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (c *ConnectPacket) Unpack(b io.Reader) error { + var err error + c.ProtocolName, err = decodeString(b) + if err != nil { + return err + } + c.ProtocolVersion, err = decodeByte(b) + if err != nil { + return err + } + options, err := decodeByte(b) + if err != nil { + return err + } + c.ReservedBit = 1 & options + c.CleanSession = 1&(options>>1) > 0 + c.WillFlag = 1&(options>>2) > 0 + c.WillQos = 3 & (options >> 3) + c.WillRetain = 1&(options>>5) > 0 + c.PasswordFlag = 1&(options>>6) > 0 + c.UsernameFlag = 1&(options>>7) > 0 + c.Keepalive, err = decodeUint16(b) + if err != nil { + return err + } + c.ClientIdentifier, err = decodeString(b) + if err != nil { + return err + } + if c.WillFlag { + c.WillTopic, err = decodeString(b) + if err != nil { + return err + } + c.WillMessage, err = decodeBytes(b) + if err != nil { + return err + } + } + if c.UsernameFlag { + c.Username, err = decodeString(b) + if err != nil { + return err + } + } + if c.PasswordFlag { + c.Password, err = decodeBytes(b) + if err != nil { + return err + } + } + + return nil +} + +//Validate performs validation of the fields of a Connect packet +func (c *ConnectPacket) Validate() byte { + if c.PasswordFlag && !c.UsernameFlag { + return ErrRefusedBadUsernameOrPassword + } + if c.ReservedBit != 0 { + //Bad reserved bit + return ErrProtocolViolation + } + if (c.ProtocolName == "MQIsdp" && c.ProtocolVersion != 3) || (c.ProtocolName == "MQTT" && c.ProtocolVersion != 4) { + //Mismatched or unsupported protocol version + return ErrRefusedBadProtocolVersion + } + if c.ProtocolName != "MQIsdp" && c.ProtocolName != "MQTT" { + //Bad protocol name + return ErrProtocolViolation + } + if len(c.ClientIdentifier) > 65535 || len(c.Username) > 65535 || len(c.Password) > 65535 { + //Bad size field + return ErrProtocolViolation + } + if len(c.ClientIdentifier) == 0 && !c.CleanSession { + //Bad client identifier + return ErrRefusedIDRejected + } + return Accepted +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (c *ConnectPacket) Details() Details { + return Details{Qos: 0, MessageID: 0} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/disconnect.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/disconnect.go new file mode 100644 index 0000000..e5c1869 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/disconnect.go @@ -0,0 +1,36 @@ +package packets + +import ( + "fmt" + "io" +) + +//DisconnectPacket is an internal representation of the fields of the +//Disconnect MQTT packet +type DisconnectPacket struct { + FixedHeader +} + +func (d *DisconnectPacket) String() string { + str := fmt.Sprintf("%s", d.FixedHeader) + return str +} + +func (d *DisconnectPacket) Write(w io.Writer) error { + packet := d.FixedHeader.pack() + _, err := packet.WriteTo(w) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (d *DisconnectPacket) Unpack(b io.Reader) error { + return nil +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (d *DisconnectPacket) Details() Details { + return Details{Qos: 0, MessageID: 0} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/packets.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/packets.go new file mode 100644 index 0000000..42eeb46 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/packets.go @@ -0,0 +1,346 @@ +package packets + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" +) + +//ControlPacket defines the interface for structs intended to hold +//decoded MQTT packets, either from being read or before being +//written +type ControlPacket interface { + Write(io.Writer) error + Unpack(io.Reader) error + String() string + Details() Details +} + +//PacketNames maps the constants for each of the MQTT packet types +//to a string representation of their name. +var PacketNames = map[uint8]string{ + 1: "CONNECT", + 2: "CONNACK", + 3: "PUBLISH", + 4: "PUBACK", + 5: "PUBREC", + 6: "PUBREL", + 7: "PUBCOMP", + 8: "SUBSCRIBE", + 9: "SUBACK", + 10: "UNSUBSCRIBE", + 11: "UNSUBACK", + 12: "PINGREQ", + 13: "PINGRESP", + 14: "DISCONNECT", +} + +//Below are the constants assigned to each of the MQTT packet types +const ( + Connect = 1 + Connack = 2 + Publish = 3 + Puback = 4 + Pubrec = 5 + Pubrel = 6 + Pubcomp = 7 + Subscribe = 8 + Suback = 9 + Unsubscribe = 10 + Unsuback = 11 + Pingreq = 12 + Pingresp = 13 + Disconnect = 14 +) + +//Below are the const definitions for error codes returned by +//Connect() +const ( + Accepted = 0x00 + ErrRefusedBadProtocolVersion = 0x01 + ErrRefusedIDRejected = 0x02 + ErrRefusedServerUnavailable = 0x03 + ErrRefusedBadUsernameOrPassword = 0x04 + ErrRefusedNotAuthorised = 0x05 + ErrNetworkError = 0xFE + ErrProtocolViolation = 0xFF +) + +//ConnackReturnCodes is a map of the error codes constants for Connect() +//to a string representation of the error +var ConnackReturnCodes = map[uint8]string{ + 0: "Connection Accepted", + 1: "Connection Refused: Bad Protocol Version", + 2: "Connection Refused: Client Identifier Rejected", + 3: "Connection Refused: Server Unavailable", + 4: "Connection Refused: Username or Password in unknown format", + 5: "Connection Refused: Not Authorised", + 254: "Connection Error", + 255: "Connection Refused: Protocol Violation", +} + +//ConnErrors is a map of the errors codes constants for Connect() +//to a Go error +var ConnErrors = map[byte]error{ + Accepted: nil, + ErrRefusedBadProtocolVersion: errors.New("Unnacceptable protocol version"), + ErrRefusedIDRejected: errors.New("Identifier rejected"), + ErrRefusedServerUnavailable: errors.New("Server Unavailable"), + ErrRefusedBadUsernameOrPassword: errors.New("Bad user name or password"), + ErrRefusedNotAuthorised: errors.New("Not Authorized"), + ErrNetworkError: errors.New("Network Error"), + ErrProtocolViolation: errors.New("Protocol Violation"), +} + +//ReadPacket takes an instance of an io.Reader (such as net.Conn) and attempts +//to read an MQTT packet from the stream. It returns a ControlPacket +//representing the decoded MQTT packet and an error. One of these returns will +//always be nil, a nil ControlPacket indicating an error occurred. +func ReadPacket(r io.Reader) (ControlPacket, error) { + var fh FixedHeader + b := make([]byte, 1) + + _, err := io.ReadFull(r, b) + if err != nil { + return nil, err + } + + err = fh.unpack(b[0], r) + if err != nil { + return nil, err + } + + cp, err := NewControlPacketWithHeader(fh) + if err != nil { + return nil, err + } + + packetBytes := make([]byte, fh.RemainingLength) + n, err := io.ReadFull(r, packetBytes) + if err != nil { + return nil, err + } + if n != fh.RemainingLength { + return nil, errors.New("Failed to read expected data") + } + + err = cp.Unpack(bytes.NewBuffer(packetBytes)) + return cp, err +} + +//NewControlPacket is used to create a new ControlPacket of the type specified +//by packetType, this is usually done by reference to the packet type constants +//defined in packets.go. The newly created ControlPacket is empty and a pointer +//is returned. +func NewControlPacket(packetType byte) ControlPacket { + switch packetType { + case Connect: + return &ConnectPacket{FixedHeader: FixedHeader{MessageType: Connect}} + case Connack: + return &ConnackPacket{FixedHeader: FixedHeader{MessageType: Connack}} + case Disconnect: + return &DisconnectPacket{FixedHeader: FixedHeader{MessageType: Disconnect}} + case Publish: + return &PublishPacket{FixedHeader: FixedHeader{MessageType: Publish}} + case Puback: + return &PubackPacket{FixedHeader: FixedHeader{MessageType: Puback}} + case Pubrec: + return &PubrecPacket{FixedHeader: FixedHeader{MessageType: Pubrec}} + case Pubrel: + return &PubrelPacket{FixedHeader: FixedHeader{MessageType: Pubrel, Qos: 1}} + case Pubcomp: + return &PubcompPacket{FixedHeader: FixedHeader{MessageType: Pubcomp}} + case Subscribe: + return &SubscribePacket{FixedHeader: FixedHeader{MessageType: Subscribe, Qos: 1}} + case Suback: + return &SubackPacket{FixedHeader: FixedHeader{MessageType: Suback}} + case Unsubscribe: + return &UnsubscribePacket{FixedHeader: FixedHeader{MessageType: Unsubscribe, Qos: 1}} + case Unsuback: + return &UnsubackPacket{FixedHeader: FixedHeader{MessageType: Unsuback}} + case Pingreq: + return &PingreqPacket{FixedHeader: FixedHeader{MessageType: Pingreq}} + case Pingresp: + return &PingrespPacket{FixedHeader: FixedHeader{MessageType: Pingresp}} + } + return nil +} + +//NewControlPacketWithHeader is used to create a new ControlPacket of the type +//specified within the FixedHeader that is passed to the function. +//The newly created ControlPacket is empty and a pointer is returned. +func NewControlPacketWithHeader(fh FixedHeader) (ControlPacket, error) { + switch fh.MessageType { + case Connect: + return &ConnectPacket{FixedHeader: fh}, nil + case Connack: + return &ConnackPacket{FixedHeader: fh}, nil + case Disconnect: + return &DisconnectPacket{FixedHeader: fh}, nil + case Publish: + return &PublishPacket{FixedHeader: fh}, nil + case Puback: + return &PubackPacket{FixedHeader: fh}, nil + case Pubrec: + return &PubrecPacket{FixedHeader: fh}, nil + case Pubrel: + return &PubrelPacket{FixedHeader: fh}, nil + case Pubcomp: + return &PubcompPacket{FixedHeader: fh}, nil + case Subscribe: + return &SubscribePacket{FixedHeader: fh}, nil + case Suback: + return &SubackPacket{FixedHeader: fh}, nil + case Unsubscribe: + return &UnsubscribePacket{FixedHeader: fh}, nil + case Unsuback: + return &UnsubackPacket{FixedHeader: fh}, nil + case Pingreq: + return &PingreqPacket{FixedHeader: fh}, nil + case Pingresp: + return &PingrespPacket{FixedHeader: fh}, nil + } + return nil, fmt.Errorf("unsupported packet type 0x%x", fh.MessageType) +} + +//Details struct returned by the Details() function called on +//ControlPackets to present details of the Qos and MessageID +//of the ControlPacket +type Details struct { + Qos byte + MessageID uint16 +} + +//FixedHeader is a struct to hold the decoded information from +//the fixed header of an MQTT ControlPacket +type FixedHeader struct { + MessageType byte + Dup bool + Qos byte + Retain bool + RemainingLength int +} + +func (fh FixedHeader) String() string { + return fmt.Sprintf("%s: dup: %t qos: %d retain: %t rLength: %d", PacketNames[fh.MessageType], fh.Dup, fh.Qos, fh.Retain, fh.RemainingLength) +} + +func boolToByte(b bool) byte { + switch b { + case true: + return 1 + default: + return 0 + } +} + +func (fh *FixedHeader) pack() bytes.Buffer { + var header bytes.Buffer + header.WriteByte(fh.MessageType<<4 | boolToByte(fh.Dup)<<3 | fh.Qos<<1 | boolToByte(fh.Retain)) + header.Write(encodeLength(fh.RemainingLength)) + return header +} + +func (fh *FixedHeader) unpack(typeAndFlags byte, r io.Reader) error { + fh.MessageType = typeAndFlags >> 4 + fh.Dup = (typeAndFlags>>3)&0x01 > 0 + fh.Qos = (typeAndFlags >> 1) & 0x03 + fh.Retain = typeAndFlags&0x01 > 0 + + var err error + fh.RemainingLength, err = decodeLength(r) + return err +} + +func decodeByte(b io.Reader) (byte, error) { + num := make([]byte, 1) + _, err := b.Read(num) + if err != nil { + return 0, err + } + + return num[0], nil +} + +func decodeUint16(b io.Reader) (uint16, error) { + num := make([]byte, 2) + _, err := b.Read(num) + if err != nil { + return 0, err + } + return binary.BigEndian.Uint16(num), nil +} + +func encodeUint16(num uint16) []byte { + bytes := make([]byte, 2) + binary.BigEndian.PutUint16(bytes, num) + return bytes +} + +func encodeString(field string) []byte { + return encodeBytes([]byte(field)) +} + +func decodeString(b io.Reader) (string, error) { + buf, err := decodeBytes(b) + return string(buf), err +} + +func decodeBytes(b io.Reader) ([]byte, error) { + fieldLength, err := decodeUint16(b) + if err != nil { + return nil, err + } + + field := make([]byte, fieldLength) + _, err = b.Read(field) + if err != nil { + return nil, err + } + + return field, nil +} + +func encodeBytes(field []byte) []byte { + fieldLength := make([]byte, 2) + binary.BigEndian.PutUint16(fieldLength, uint16(len(field))) + return append(fieldLength, field...) +} + +func encodeLength(length int) []byte { + var encLength []byte + for { + digit := byte(length % 128) + length /= 128 + if length > 0 { + digit |= 0x80 + } + encLength = append(encLength, digit) + if length == 0 { + break + } + } + return encLength +} + +func decodeLength(r io.Reader) (int, error) { + var rLength uint32 + var multiplier uint32 + b := make([]byte, 1) + for multiplier < 27 { //fix: Infinite '(digit & 128) == 1' will cause the dead loop + _, err := io.ReadFull(r, b) + if err != nil { + return 0, err + } + + digit := b[0] + rLength |= uint32(digit&127) << multiplier + if (digit & 128) == 0 { + break + } + multiplier += 7 + } + return int(rLength), nil +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/pingreq.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/pingreq.go new file mode 100644 index 0000000..5c3e88f --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/pingreq.go @@ -0,0 +1,36 @@ +package packets + +import ( + "fmt" + "io" +) + +//PingreqPacket is an internal representation of the fields of the +//Pingreq MQTT packet +type PingreqPacket struct { + FixedHeader +} + +func (pr *PingreqPacket) String() string { + str := fmt.Sprintf("%s", pr.FixedHeader) + return str +} + +func (pr *PingreqPacket) Write(w io.Writer) error { + packet := pr.FixedHeader.pack() + _, err := packet.WriteTo(w) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (pr *PingreqPacket) Unpack(b io.Reader) error { + return nil +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (pr *PingreqPacket) Details() Details { + return Details{Qos: 0, MessageID: 0} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/pingresp.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/pingresp.go new file mode 100644 index 0000000..39ebc00 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/pingresp.go @@ -0,0 +1,36 @@ +package packets + +import ( + "fmt" + "io" +) + +//PingrespPacket is an internal representation of the fields of the +//Pingresp MQTT packet +type PingrespPacket struct { + FixedHeader +} + +func (pr *PingrespPacket) String() string { + str := fmt.Sprintf("%s", pr.FixedHeader) + return str +} + +func (pr *PingrespPacket) Write(w io.Writer) error { + packet := pr.FixedHeader.pack() + _, err := packet.WriteTo(w) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (pr *PingrespPacket) Unpack(b io.Reader) error { + return nil +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (pr *PingrespPacket) Details() Details { + return Details{Qos: 0, MessageID: 0} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/puback.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/puback.go new file mode 100644 index 0000000..7c0cd7e --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/puback.go @@ -0,0 +1,45 @@ +package packets + +import ( + "fmt" + "io" +) + +//PubackPacket is an internal representation of the fields of the +//Puback MQTT packet +type PubackPacket struct { + FixedHeader + MessageID uint16 +} + +func (pa *PubackPacket) String() string { + str := fmt.Sprintf("%s", pa.FixedHeader) + str += " " + str += fmt.Sprintf("MessageID: %d", pa.MessageID) + return str +} + +func (pa *PubackPacket) Write(w io.Writer) error { + var err error + pa.FixedHeader.RemainingLength = 2 + packet := pa.FixedHeader.pack() + packet.Write(encodeUint16(pa.MessageID)) + _, err = packet.WriteTo(w) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (pa *PubackPacket) Unpack(b io.Reader) error { + var err error + pa.MessageID, err = decodeUint16(b) + + return err +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (pa *PubackPacket) Details() Details { + return Details{Qos: pa.Qos, MessageID: pa.MessageID} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/pubcomp.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/pubcomp.go new file mode 100644 index 0000000..4f6f6e2 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/pubcomp.go @@ -0,0 +1,45 @@ +package packets + +import ( + "fmt" + "io" +) + +//PubcompPacket is an internal representation of the fields of the +//Pubcomp MQTT packet +type PubcompPacket struct { + FixedHeader + MessageID uint16 +} + +func (pc *PubcompPacket) String() string { + str := fmt.Sprintf("%s", pc.FixedHeader) + str += " " + str += fmt.Sprintf("MessageID: %d", pc.MessageID) + return str +} + +func (pc *PubcompPacket) Write(w io.Writer) error { + var err error + pc.FixedHeader.RemainingLength = 2 + packet := pc.FixedHeader.pack() + packet.Write(encodeUint16(pc.MessageID)) + _, err = packet.WriteTo(w) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (pc *PubcompPacket) Unpack(b io.Reader) error { + var err error + pc.MessageID, err = decodeUint16(b) + + return err +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (pc *PubcompPacket) Details() Details { + return Details{Qos: pc.Qos, MessageID: pc.MessageID} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/publish.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/publish.go new file mode 100644 index 0000000..adc9adb --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/publish.go @@ -0,0 +1,88 @@ +package packets + +import ( + "bytes" + "fmt" + "io" +) + +//PublishPacket is an internal representation of the fields of the +//Publish MQTT packet +type PublishPacket struct { + FixedHeader + TopicName string + MessageID uint16 + Payload []byte +} + +func (p *PublishPacket) String() string { + str := fmt.Sprintf("%s", p.FixedHeader) + str += " " + str += fmt.Sprintf("topicName: %s MessageID: %d", p.TopicName, p.MessageID) + str += " " + str += fmt.Sprintf("payload: %s", string(p.Payload)) + return str +} + +func (p *PublishPacket) Write(w io.Writer) error { + var body bytes.Buffer + var err error + + body.Write(encodeString(p.TopicName)) + if p.Qos > 0 { + body.Write(encodeUint16(p.MessageID)) + } + p.FixedHeader.RemainingLength = body.Len() + len(p.Payload) + packet := p.FixedHeader.pack() + packet.Write(body.Bytes()) + packet.Write(p.Payload) + _, err = w.Write(packet.Bytes()) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (p *PublishPacket) Unpack(b io.Reader) error { + var payloadLength = p.FixedHeader.RemainingLength + var err error + p.TopicName, err = decodeString(b) + if err != nil { + return err + } + + if p.Qos > 0 { + p.MessageID, err = decodeUint16(b) + if err != nil { + return err + } + payloadLength -= len(p.TopicName) + 4 + } else { + payloadLength -= len(p.TopicName) + 2 + } + if payloadLength < 0 { + return fmt.Errorf("Error unpacking publish, payload length < 0") + } + p.Payload = make([]byte, payloadLength) + _, err = b.Read(p.Payload) + + return err +} + +//Copy creates a new PublishPacket with the same topic and payload +//but an empty fixed header, useful for when you want to deliver +//a message with different properties such as Qos but the same +//content +func (p *PublishPacket) Copy() *PublishPacket { + newP := NewControlPacket(Publish).(*PublishPacket) + newP.TopicName = p.TopicName + newP.Payload = p.Payload + + return newP +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (p *PublishPacket) Details() Details { + return Details{Qos: p.Qos, MessageID: p.MessageID} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/pubrec.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/pubrec.go new file mode 100644 index 0000000..483372b --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/pubrec.go @@ -0,0 +1,45 @@ +package packets + +import ( + "fmt" + "io" +) + +//PubrecPacket is an internal representation of the fields of the +//Pubrec MQTT packet +type PubrecPacket struct { + FixedHeader + MessageID uint16 +} + +func (pr *PubrecPacket) String() string { + str := fmt.Sprintf("%s", pr.FixedHeader) + str += " " + str += fmt.Sprintf("MessageID: %d", pr.MessageID) + return str +} + +func (pr *PubrecPacket) Write(w io.Writer) error { + var err error + pr.FixedHeader.RemainingLength = 2 + packet := pr.FixedHeader.pack() + packet.Write(encodeUint16(pr.MessageID)) + _, err = packet.WriteTo(w) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (pr *PubrecPacket) Unpack(b io.Reader) error { + var err error + pr.MessageID, err = decodeUint16(b) + + return err +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (pr *PubrecPacket) Details() Details { + return Details{Qos: pr.Qos, MessageID: pr.MessageID} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/pubrel.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/pubrel.go new file mode 100644 index 0000000..8590fd9 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/pubrel.go @@ -0,0 +1,45 @@ +package packets + +import ( + "fmt" + "io" +) + +//PubrelPacket is an internal representation of the fields of the +//Pubrel MQTT packet +type PubrelPacket struct { + FixedHeader + MessageID uint16 +} + +func (pr *PubrelPacket) String() string { + str := fmt.Sprintf("%s", pr.FixedHeader) + str += " " + str += fmt.Sprintf("MessageID: %d", pr.MessageID) + return str +} + +func (pr *PubrelPacket) Write(w io.Writer) error { + var err error + pr.FixedHeader.RemainingLength = 2 + packet := pr.FixedHeader.pack() + packet.Write(encodeUint16(pr.MessageID)) + _, err = packet.WriteTo(w) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (pr *PubrelPacket) Unpack(b io.Reader) error { + var err error + pr.MessageID, err = decodeUint16(b) + + return err +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (pr *PubrelPacket) Details() Details { + return Details{Qos: pr.Qos, MessageID: pr.MessageID} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/suback.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/suback.go new file mode 100644 index 0000000..fc05724 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/suback.go @@ -0,0 +1,60 @@ +package packets + +import ( + "bytes" + "fmt" + "io" +) + +//SubackPacket is an internal representation of the fields of the +//Suback MQTT packet +type SubackPacket struct { + FixedHeader + MessageID uint16 + ReturnCodes []byte +} + +func (sa *SubackPacket) String() string { + str := fmt.Sprintf("%s", sa.FixedHeader) + str += " " + str += fmt.Sprintf("MessageID: %d", sa.MessageID) + return str +} + +func (sa *SubackPacket) Write(w io.Writer) error { + var body bytes.Buffer + var err error + body.Write(encodeUint16(sa.MessageID)) + body.Write(sa.ReturnCodes) + sa.FixedHeader.RemainingLength = body.Len() + packet := sa.FixedHeader.pack() + packet.Write(body.Bytes()) + _, err = packet.WriteTo(w) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (sa *SubackPacket) Unpack(b io.Reader) error { + var qosBuffer bytes.Buffer + var err error + sa.MessageID, err = decodeUint16(b) + if err != nil { + return err + } + + _, err = qosBuffer.ReadFrom(b) + if err != nil { + return err + } + sa.ReturnCodes = qosBuffer.Bytes() + + return nil +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (sa *SubackPacket) Details() Details { + return Details{Qos: 0, MessageID: sa.MessageID} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/subscribe.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/subscribe.go new file mode 100644 index 0000000..0787ce0 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/subscribe.go @@ -0,0 +1,72 @@ +package packets + +import ( + "bytes" + "fmt" + "io" +) + +//SubscribePacket is an internal representation of the fields of the +//Subscribe MQTT packet +type SubscribePacket struct { + FixedHeader + MessageID uint16 + Topics []string + Qoss []byte +} + +func (s *SubscribePacket) String() string { + str := fmt.Sprintf("%s", s.FixedHeader) + str += " " + str += fmt.Sprintf("MessageID: %d topics: %s", s.MessageID, s.Topics) + return str +} + +func (s *SubscribePacket) Write(w io.Writer) error { + var body bytes.Buffer + var err error + + body.Write(encodeUint16(s.MessageID)) + for i, topic := range s.Topics { + body.Write(encodeString(topic)) + body.WriteByte(s.Qoss[i]) + } + s.FixedHeader.RemainingLength = body.Len() + packet := s.FixedHeader.pack() + packet.Write(body.Bytes()) + _, err = packet.WriteTo(w) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (s *SubscribePacket) Unpack(b io.Reader) error { + var err error + s.MessageID, err = decodeUint16(b) + if err != nil { + return err + } + payloadLength := s.FixedHeader.RemainingLength - 2 + for payloadLength > 0 { + topic, err := decodeString(b) + if err != nil { + return err + } + s.Topics = append(s.Topics, topic) + qos, err := decodeByte(b) + if err != nil { + return err + } + s.Qoss = append(s.Qoss, qos) + payloadLength -= 2 + len(topic) + 1 //2 bytes of string length, plus string, plus 1 byte for Qos + } + + return nil +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (s *SubscribePacket) Details() Details { + return Details{Qos: 1, MessageID: s.MessageID} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/unsuback.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/unsuback.go new file mode 100644 index 0000000..4b40c27 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/unsuback.go @@ -0,0 +1,45 @@ +package packets + +import ( + "fmt" + "io" +) + +//UnsubackPacket is an internal representation of the fields of the +//Unsuback MQTT packet +type UnsubackPacket struct { + FixedHeader + MessageID uint16 +} + +func (ua *UnsubackPacket) String() string { + str := fmt.Sprintf("%s", ua.FixedHeader) + str += " " + str += fmt.Sprintf("MessageID: %d", ua.MessageID) + return str +} + +func (ua *UnsubackPacket) Write(w io.Writer) error { + var err error + ua.FixedHeader.RemainingLength = 2 + packet := ua.FixedHeader.pack() + packet.Write(encodeUint16(ua.MessageID)) + _, err = packet.WriteTo(w) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (ua *UnsubackPacket) Unpack(b io.Reader) error { + var err error + ua.MessageID, err = decodeUint16(b) + + return err +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (ua *UnsubackPacket) Details() Details { + return Details{Qos: 0, MessageID: ua.MessageID} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/packets/unsubscribe.go b/vendor/github.com/eclipse/paho.mqtt.golang/packets/unsubscribe.go new file mode 100644 index 0000000..2012c31 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/packets/unsubscribe.go @@ -0,0 +1,59 @@ +package packets + +import ( + "bytes" + "fmt" + "io" +) + +//UnsubscribePacket is an internal representation of the fields of the +//Unsubscribe MQTT packet +type UnsubscribePacket struct { + FixedHeader + MessageID uint16 + Topics []string +} + +func (u *UnsubscribePacket) String() string { + str := fmt.Sprintf("%s", u.FixedHeader) + str += " " + str += fmt.Sprintf("MessageID: %d", u.MessageID) + return str +} + +func (u *UnsubscribePacket) Write(w io.Writer) error { + var body bytes.Buffer + var err error + body.Write(encodeUint16(u.MessageID)) + for _, topic := range u.Topics { + body.Write(encodeString(topic)) + } + u.FixedHeader.RemainingLength = body.Len() + packet := u.FixedHeader.pack() + packet.Write(body.Bytes()) + _, err = packet.WriteTo(w) + + return err +} + +//Unpack decodes the details of a ControlPacket after the fixed +//header has been read +func (u *UnsubscribePacket) Unpack(b io.Reader) error { + var err error + u.MessageID, err = decodeUint16(b) + if err != nil { + return err + } + + for topic, err := decodeString(b); err == nil && topic != ""; topic, err = decodeString(b) { + u.Topics = append(u.Topics, topic) + } + + return err +} + +//Details returns a Details struct containing the Qos and +//MessageID of this ControlPacket +func (u *UnsubscribePacket) Details() Details { + return Details{Qos: 1, MessageID: u.MessageID} +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/ping.go b/vendor/github.com/eclipse/paho.mqtt.golang/ping.go new file mode 100644 index 0000000..dcbcb1d --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/ping.go @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +package mqtt + +import ( + "errors" + "sync/atomic" + "time" + + "github.com/eclipse/paho.mqtt.golang/packets" +) + +func keepalive(c *client) { + defer c.workers.Done() + DEBUG.Println(PNG, "keepalive starting") + var checkInterval int64 + var pingSent time.Time + + if c.options.KeepAlive > 10 { + checkInterval = 5 + } else { + checkInterval = c.options.KeepAlive / 2 + } + + intervalTicker := time.NewTicker(time.Duration(checkInterval * int64(time.Second))) + defer intervalTicker.Stop() + + for { + select { + case <-c.stop: + DEBUG.Println(PNG, "keepalive stopped") + return + case <-intervalTicker.C: + lastSent := c.lastSent.Load().(time.Time) + lastReceived := c.lastReceived.Load().(time.Time) + + DEBUG.Println(PNG, "ping check", time.Since(lastSent).Seconds()) + if time.Since(lastSent) >= time.Duration(c.options.KeepAlive*int64(time.Second)) || time.Since(lastReceived) >= time.Duration(c.options.KeepAlive*int64(time.Second)) { + if atomic.LoadInt32(&c.pingOutstanding) == 0 { + DEBUG.Println(PNG, "keepalive sending ping") + ping := packets.NewControlPacket(packets.Pingreq).(*packets.PingreqPacket) + //We don't want to wait behind large messages being sent, the Write call + //will block until it it able to send the packet. + atomic.StoreInt32(&c.pingOutstanding, 1) + ping.Write(c.conn) + c.lastSent.Store(time.Now()) + pingSent = time.Now() + } + } + if atomic.LoadInt32(&c.pingOutstanding) > 0 && time.Now().Sub(pingSent) >= c.options.PingTimeout { + CRITICAL.Println(PNG, "pingresp not received, disconnecting") + c.errors <- errors.New("pingresp not received, disconnecting") + return + } + } + } +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/router.go b/vendor/github.com/eclipse/paho.mqtt.golang/router.go new file mode 100644 index 0000000..7b4e8f8 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/router.go @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +package mqtt + +import ( + "container/list" + "strings" + "sync" + + "github.com/eclipse/paho.mqtt.golang/packets" +) + +// route is a type which associates MQTT Topic strings with a +// callback to be executed upon the arrival of a message associated +// with a subscription to that topic. +type route struct { + topic string + callback MessageHandler +} + +// match takes a slice of strings which represent the route being tested having been split on '/' +// separators, and a slice of strings representing the topic string in the published message, similarly +// split. +// The function determines if the topic string matches the route according to the MQTT topic rules +// and returns a boolean of the outcome +func match(route []string, topic []string) bool { + if len(route) == 0 { + if len(topic) == 0 { + return true + } + return false + } + + if len(topic) == 0 { + if route[0] == "#" { + return true + } + return false + } + + if route[0] == "#" { + return true + } + + if (route[0] == "+") || (route[0] == topic[0]) { + return match(route[1:], topic[1:]) + } + return false +} + +func routeIncludesTopic(route, topic string) bool { + return match(routeSplit(route), strings.Split(topic, "/")) +} + +// removes $share and sharename when splitting the route to allow +// shared subscription routes to correctly match the topic +func routeSplit(route string) []string { + var result []string + if strings.HasPrefix(route, "$share") { + result = strings.Split(route, "/")[2:] + } else { + result = strings.Split(route, "/") + } + return result +} + +// match takes the topic string of the published message and does a basic compare to the +// string of the current Route, if they match it returns true +func (r *route) match(topic string) bool { + return r.topic == topic || routeIncludesTopic(r.topic, topic) +} + +type router struct { + sync.RWMutex + routes *list.List + defaultHandler MessageHandler + messages chan *packets.PublishPacket + stop chan bool +} + +// newRouter returns a new instance of a Router and channel which can be used to tell the Router +// to stop +func newRouter() (*router, chan bool) { + router := &router{routes: list.New(), messages: make(chan *packets.PublishPacket), stop: make(chan bool)} + stop := router.stop + return router, stop +} + +// addRoute takes a topic string and MessageHandler callback. It looks in the current list of +// routes to see if there is already a matching Route. If there is it replaces the current +// callback with the new one. If not it add a new entry to the list of Routes. +func (r *router) addRoute(topic string, callback MessageHandler) { + r.Lock() + defer r.Unlock() + for e := r.routes.Front(); e != nil; e = e.Next() { + if e.Value.(*route).match(topic) { + r := e.Value.(*route) + r.callback = callback + return + } + } + r.routes.PushBack(&route{topic: topic, callback: callback}) +} + +// deleteRoute takes a route string, looks for a matching Route in the list of Routes. If +// found it removes the Route from the list. +func (r *router) deleteRoute(topic string) { + r.Lock() + defer r.Unlock() + for e := r.routes.Front(); e != nil; e = e.Next() { + if e.Value.(*route).match(topic) { + r.routes.Remove(e) + return + } + } +} + +// setDefaultHandler assigns a default callback that will be called if no matching Route +// is found for an incoming Publish. +func (r *router) setDefaultHandler(handler MessageHandler) { + r.Lock() + defer r.Unlock() + r.defaultHandler = handler +} + +// matchAndDispatch takes a channel of Message pointers as input and starts a go routine that +// takes messages off the channel, matches them against the internal route list and calls the +// associated callback (or the defaultHandler, if one exists and no other route matched). If +// anything is sent down the stop channel the function will end. +func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order bool, client *client) { + go func() { + for { + select { + case message := <-messages: + sent := false + r.RLock() + m := messageFromPublish(message, client.ackFunc(message)) + handlers := []MessageHandler{} + for e := r.routes.Front(); e != nil; e = e.Next() { + if e.Value.(*route).match(message.TopicName) { + if order { + handlers = append(handlers, e.Value.(*route).callback) + } else { + hd := e.Value.(*route).callback + go func() { + hd(client, m) + m.Ack() + }() + } + sent = true + } + } + if !sent && r.defaultHandler != nil { + if order { + handlers = append(handlers, r.defaultHandler) + } else { + go func() { + r.defaultHandler(client, m) + m.Ack() + }() + } + } + r.RUnlock() + for _, handler := range handlers { + func() { + handler(client, m) + m.Ack() + }() + } + case <-r.stop: + return + } + } + }() +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/store.go b/vendor/github.com/eclipse/paho.mqtt.golang/store.go new file mode 100644 index 0000000..24a76b7 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/store.go @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +package mqtt + +import ( + "fmt" + "strconv" + + "github.com/eclipse/paho.mqtt.golang/packets" +) + +const ( + inboundPrefix = "i." + outboundPrefix = "o." +) + +// Store is an interface which can be used to provide implementations +// for message persistence. +// Because we may have to store distinct messages with the same +// message ID, we need a unique key for each message. This is +// possible by prepending "i." or "o." to each message id +type Store interface { + Open() + Put(key string, message packets.ControlPacket) + Get(key string) packets.ControlPacket + All() []string + Del(key string) + Close() + Reset() +} + +// A key MUST have the form "X.[messageid]" +// where X is 'i' or 'o' +func mIDFromKey(key string) uint16 { + s := key[2:] + i, err := strconv.Atoi(s) + chkerr(err) + return uint16(i) +} + +// Return true if key prefix is outbound +func isKeyOutbound(key string) bool { + return key[:2] == outboundPrefix +} + +// Return true if key prefix is inbound +func isKeyInbound(key string) bool { + return key[:2] == inboundPrefix +} + +// Return a string of the form "i.[id]" +func inboundKeyFromMID(id uint16) string { + return fmt.Sprintf("%s%d", inboundPrefix, id) +} + +// Return a string of the form "o.[id]" +func outboundKeyFromMID(id uint16) string { + return fmt.Sprintf("%s%d", outboundPrefix, id) +} + +// govern which outgoing messages are persisted +func persistOutbound(s Store, m packets.ControlPacket) { + switch m.Details().Qos { + case 0: + switch m.(type) { + case *packets.PubackPacket, *packets.PubcompPacket: + // Sending puback. delete matching publish + // from ibound + s.Del(inboundKeyFromMID(m.Details().MessageID)) + } + case 1: + switch m.(type) { + case *packets.PublishPacket, *packets.PubrelPacket, *packets.SubscribePacket, *packets.UnsubscribePacket: + // Sending publish. store in obound + // until puback received + s.Put(outboundKeyFromMID(m.Details().MessageID), m) + default: + ERROR.Println(STR, "Asked to persist an invalid message type") + } + case 2: + switch m.(type) { + case *packets.PublishPacket: + // Sending publish. store in obound + // until pubrel received + s.Put(outboundKeyFromMID(m.Details().MessageID), m) + default: + ERROR.Println(STR, "Asked to persist an invalid message type") + } + } +} + +// govern which incoming messages are persisted +func persistInbound(s Store, m packets.ControlPacket) { + switch m.Details().Qos { + case 0: + switch m.(type) { + case *packets.PubackPacket, *packets.SubackPacket, *packets.UnsubackPacket, *packets.PubcompPacket: + // Received a puback. delete matching publish + // from obound + s.Del(outboundKeyFromMID(m.Details().MessageID)) + case *packets.PublishPacket, *packets.PubrecPacket, *packets.PingrespPacket, *packets.ConnackPacket: + default: + ERROR.Println(STR, "Asked to persist an invalid messages type") + } + case 1: + switch m.(type) { + case *packets.PublishPacket, *packets.PubrelPacket: + // Received a publish. store it in ibound + // until puback sent + s.Put(inboundKeyFromMID(m.Details().MessageID), m) + default: + ERROR.Println(STR, "Asked to persist an invalid messages type") + } + case 2: + switch m.(type) { + case *packets.PublishPacket: + // Received a publish. store it in ibound + // until pubrel received + s.Put(inboundKeyFromMID(m.Details().MessageID), m) + default: + ERROR.Println(STR, "Asked to persist an invalid messages type") + } + } +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/token.go b/vendor/github.com/eclipse/paho.mqtt.golang/token.go new file mode 100644 index 0000000..0818553 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/token.go @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Allan Stockdill-Mander + */ + +package mqtt + +import ( + "sync" + "time" + + "github.com/eclipse/paho.mqtt.golang/packets" +) + +// PacketAndToken is a struct that contains both a ControlPacket and a +// Token. This struct is passed via channels between the client interface +// code and the underlying code responsible for sending and receiving +// MQTT messages. +type PacketAndToken struct { + p packets.ControlPacket + t tokenCompletor +} + +// Token defines the interface for the tokens used to indicate when +// actions have completed. +type Token interface { + Wait() bool + WaitTimeout(time.Duration) bool + Error() error +} + +type TokenErrorSetter interface { + setError(error) +} + +type tokenCompletor interface { + Token + TokenErrorSetter + flowComplete() +} + +type baseToken struct { + m sync.RWMutex + complete chan struct{} + err error +} + +// Wait will wait indefinitely for the Token to complete, ie the Publish +// to be sent and confirmed receipt from the broker +func (b *baseToken) Wait() bool { + <-b.complete + return true +} + +// WaitTimeout takes a time.Duration to wait for the flow associated with the +// Token to complete, returns true if it returned before the timeout or +// returns false if the timeout occurred. In the case of a timeout the Token +// does not have an error set in case the caller wishes to wait again +func (b *baseToken) WaitTimeout(d time.Duration) bool { + b.m.Lock() + defer b.m.Unlock() + + timer := time.NewTimer(d) + select { + case <-b.complete: + if !timer.Stop() { + <-timer.C + } + return true + case <-timer.C: + } + + return false +} + +func (b *baseToken) flowComplete() { + select { + case <-b.complete: + default: + close(b.complete) + } +} + +func (b *baseToken) Error() error { + b.m.RLock() + defer b.m.RUnlock() + return b.err +} + +func (b *baseToken) setError(e error) { + b.m.Lock() + b.err = e + b.flowComplete() + b.m.Unlock() +} + +func newToken(tType byte) tokenCompletor { + switch tType { + case packets.Connect: + return &ConnectToken{baseToken: baseToken{complete: make(chan struct{})}} + case packets.Subscribe: + return &SubscribeToken{baseToken: baseToken{complete: make(chan struct{})}, subResult: make(map[string]byte)} + case packets.Publish: + return &PublishToken{baseToken: baseToken{complete: make(chan struct{})}} + case packets.Unsubscribe: + return &UnsubscribeToken{baseToken: baseToken{complete: make(chan struct{})}} + case packets.Disconnect: + return &DisconnectToken{baseToken: baseToken{complete: make(chan struct{})}} + } + return nil +} + +// ConnectToken is an extension of Token containing the extra fields +// required to provide information about calls to Connect() +type ConnectToken struct { + baseToken + returnCode byte + sessionPresent bool +} + +// ReturnCode returns the acknowlegement code in the connack sent +// in response to a Connect() +func (c *ConnectToken) ReturnCode() byte { + c.m.RLock() + defer c.m.RUnlock() + return c.returnCode +} + +// SessionPresent returns a bool representing the value of the +// session present field in the connack sent in response to a Connect() +func (c *ConnectToken) SessionPresent() bool { + c.m.RLock() + defer c.m.RUnlock() + return c.sessionPresent +} + +// PublishToken is an extension of Token containing the extra fields +// required to provide information about calls to Publish() +type PublishToken struct { + baseToken + messageID uint16 +} + +// MessageID returns the MQTT message ID that was assigned to the +// Publish packet when it was sent to the broker +func (p *PublishToken) MessageID() uint16 { + return p.messageID +} + +// SubscribeToken is an extension of Token containing the extra fields +// required to provide information about calls to Subscribe() +type SubscribeToken struct { + baseToken + subs []string + subResult map[string]byte +} + +// Result returns a map of topics that were subscribed to along with +// the matching return code from the broker. This is either the Qos +// value of the subscription or an error code. +func (s *SubscribeToken) Result() map[string]byte { + s.m.RLock() + defer s.m.RUnlock() + return s.subResult +} + +// UnsubscribeToken is an extension of Token containing the extra fields +// required to provide information about calls to Unsubscribe() +type UnsubscribeToken struct { + baseToken +} + +// DisconnectToken is an extension of Token containing the extra fields +// required to provide information about calls to Disconnect() +type DisconnectToken struct { + baseToken +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/topic.go b/vendor/github.com/eclipse/paho.mqtt.golang/topic.go new file mode 100644 index 0000000..6fa3ad2 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/topic.go @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +package mqtt + +import ( + "errors" + "strings" +) + +//ErrInvalidQos is the error returned when an packet is to be sent +//with an invalid Qos value +var ErrInvalidQos = errors.New("Invalid QoS") + +//ErrInvalidTopicEmptyString is the error returned when a topic string +//is passed in that is 0 length +var ErrInvalidTopicEmptyString = errors.New("Invalid Topic; empty string") + +//ErrInvalidTopicMultilevel is the error returned when a topic string +//is passed in that has the multi level wildcard in any position but +//the last +var ErrInvalidTopicMultilevel = errors.New("Invalid Topic; multi-level wildcard must be last level") + +// Topic Names and Topic Filters +// The MQTT v3.1.1 spec clarifies a number of ambiguities with regard +// to the validity of Topic strings. +// - A Topic must be between 1 and 65535 bytes. +// - A Topic is case sensitive. +// - A Topic may contain whitespace. +// - A Topic containing a leading forward slash is different than a Topic without. +// - A Topic may be "/" (two levels, both empty string). +// - A Topic must be UTF-8 encoded. +// - A Topic may contain any number of levels. +// - A Topic may contain an empty level (two forward slashes in a row). +// - A TopicName may not contain a wildcard. +// - A TopicFilter may only have a # (multi-level) wildcard as the last level. +// - A TopicFilter may contain any number of + (single-level) wildcards. +// - A TopicFilter with a # will match the absense of a level +// Example: a subscription to "foo/#" will match messages published to "foo". + +func validateSubscribeMap(subs map[string]byte) ([]string, []byte, error) { + var topics []string + var qoss []byte + for topic, qos := range subs { + if err := validateTopicAndQos(topic, qos); err != nil { + return nil, nil, err + } + topics = append(topics, topic) + qoss = append(qoss, qos) + } + + return topics, qoss, nil +} + +func validateTopicAndQos(topic string, qos byte) error { + if len(topic) == 0 { + return ErrInvalidTopicEmptyString + } + + levels := strings.Split(topic, "/") + for i, level := range levels { + if level == "#" && i != len(levels)-1 { + return ErrInvalidTopicMultilevel + } + } + + if qos < 0 || qos > 2 { + return ErrInvalidQos + } + return nil +} diff --git a/vendor/github.com/eclipse/paho.mqtt.golang/trace.go b/vendor/github.com/eclipse/paho.mqtt.golang/trace.go new file mode 100644 index 0000000..195c817 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang/trace.go @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Seth Hoenig + * Allan Stockdill-Mander + * Mike Robertson + */ + +package mqtt + +type ( + // Logger interface allows implementations to provide to this package any + // object that implements the methods defined in it. + Logger interface { + Println(v ...interface{}) + Printf(format string, v ...interface{}) + } + + // NOOPLogger implements the logger that does not perform any operation + // by default. This allows us to efficiently discard the unwanted messages. + NOOPLogger struct{} +) + +func (NOOPLogger) Println(v ...interface{}) {} +func (NOOPLogger) Printf(format string, v ...interface{}) {} + +// Internal levels of library output that are initialised to not print +// anything but can be overridden by programmer +var ( + ERROR Logger = NOOPLogger{} + CRITICAL Logger = NOOPLogger{} + WARN Logger = NOOPLogger{} + DEBUG Logger = NOOPLogger{} +) diff --git a/vendor/golang.org/x/net/AUTHORS b/vendor/golang.org/x/net/AUTHORS new file mode 100644 index 0000000..15167cd --- /dev/null +++ b/vendor/golang.org/x/net/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/net/CONTRIBUTORS b/vendor/golang.org/x/net/CONTRIBUTORS new file mode 100644 index 0000000..1c4577e --- /dev/null +++ b/vendor/golang.org/x/net/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/net/LICENSE b/vendor/golang.org/x/net/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/vendor/golang.org/x/net/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/net/PATENTS b/vendor/golang.org/x/net/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/vendor/golang.org/x/net/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/net/internal/socks/client.go b/vendor/golang.org/x/net/internal/socks/client.go new file mode 100644 index 0000000..3d6f516 --- /dev/null +++ b/vendor/golang.org/x/net/internal/socks/client.go @@ -0,0 +1,168 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package socks + +import ( + "context" + "errors" + "io" + "net" + "strconv" + "time" +) + +var ( + noDeadline = time.Time{} + aLongTimeAgo = time.Unix(1, 0) +) + +func (d *Dialer) connect(ctx context.Context, c net.Conn, address string) (_ net.Addr, ctxErr error) { + host, port, err := splitHostPort(address) + if err != nil { + return nil, err + } + if deadline, ok := ctx.Deadline(); ok && !deadline.IsZero() { + c.SetDeadline(deadline) + defer c.SetDeadline(noDeadline) + } + if ctx != context.Background() { + errCh := make(chan error, 1) + done := make(chan struct{}) + defer func() { + close(done) + if ctxErr == nil { + ctxErr = <-errCh + } + }() + go func() { + select { + case <-ctx.Done(): + c.SetDeadline(aLongTimeAgo) + errCh <- ctx.Err() + case <-done: + errCh <- nil + } + }() + } + + b := make([]byte, 0, 6+len(host)) // the size here is just an estimate + b = append(b, Version5) + if len(d.AuthMethods) == 0 || d.Authenticate == nil { + b = append(b, 1, byte(AuthMethodNotRequired)) + } else { + ams := d.AuthMethods + if len(ams) > 255 { + return nil, errors.New("too many authentication methods") + } + b = append(b, byte(len(ams))) + for _, am := range ams { + b = append(b, byte(am)) + } + } + if _, ctxErr = c.Write(b); ctxErr != nil { + return + } + + if _, ctxErr = io.ReadFull(c, b[:2]); ctxErr != nil { + return + } + if b[0] != Version5 { + return nil, errors.New("unexpected protocol version " + strconv.Itoa(int(b[0]))) + } + am := AuthMethod(b[1]) + if am == AuthMethodNoAcceptableMethods { + return nil, errors.New("no acceptable authentication methods") + } + if d.Authenticate != nil { + if ctxErr = d.Authenticate(ctx, c, am); ctxErr != nil { + return + } + } + + b = b[:0] + b = append(b, Version5, byte(d.cmd), 0) + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + b = append(b, AddrTypeIPv4) + b = append(b, ip4...) + } else if ip6 := ip.To16(); ip6 != nil { + b = append(b, AddrTypeIPv6) + b = append(b, ip6...) + } else { + return nil, errors.New("unknown address type") + } + } else { + if len(host) > 255 { + return nil, errors.New("FQDN too long") + } + b = append(b, AddrTypeFQDN) + b = append(b, byte(len(host))) + b = append(b, host...) + } + b = append(b, byte(port>>8), byte(port)) + if _, ctxErr = c.Write(b); ctxErr != nil { + return + } + + if _, ctxErr = io.ReadFull(c, b[:4]); ctxErr != nil { + return + } + if b[0] != Version5 { + return nil, errors.New("unexpected protocol version " + strconv.Itoa(int(b[0]))) + } + if cmdErr := Reply(b[1]); cmdErr != StatusSucceeded { + return nil, errors.New("unknown error " + cmdErr.String()) + } + if b[2] != 0 { + return nil, errors.New("non-zero reserved field") + } + l := 2 + var a Addr + switch b[3] { + case AddrTypeIPv4: + l += net.IPv4len + a.IP = make(net.IP, net.IPv4len) + case AddrTypeIPv6: + l += net.IPv6len + a.IP = make(net.IP, net.IPv6len) + case AddrTypeFQDN: + if _, err := io.ReadFull(c, b[:1]); err != nil { + return nil, err + } + l += int(b[0]) + default: + return nil, errors.New("unknown address type " + strconv.Itoa(int(b[3]))) + } + if cap(b) < l { + b = make([]byte, l) + } else { + b = b[:l] + } + if _, ctxErr = io.ReadFull(c, b); ctxErr != nil { + return + } + if a.IP != nil { + copy(a.IP, b) + } else { + a.Name = string(b[:len(b)-2]) + } + a.Port = int(b[len(b)-2])<<8 | int(b[len(b)-1]) + return &a, nil +} + +func splitHostPort(address string) (string, int, error) { + host, port, err := net.SplitHostPort(address) + if err != nil { + return "", 0, err + } + portnum, err := strconv.Atoi(port) + if err != nil { + return "", 0, err + } + if 1 > portnum || portnum > 0xffff { + return "", 0, errors.New("port number out of range " + port) + } + return host, portnum, nil +} diff --git a/vendor/golang.org/x/net/internal/socks/socks.go b/vendor/golang.org/x/net/internal/socks/socks.go new file mode 100644 index 0000000..97db234 --- /dev/null +++ b/vendor/golang.org/x/net/internal/socks/socks.go @@ -0,0 +1,317 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package socks provides a SOCKS version 5 client implementation. +// +// SOCKS protocol version 5 is defined in RFC 1928. +// Username/Password authentication for SOCKS version 5 is defined in +// RFC 1929. +package socks + +import ( + "context" + "errors" + "io" + "net" + "strconv" +) + +// A Command represents a SOCKS command. +type Command int + +func (cmd Command) String() string { + switch cmd { + case CmdConnect: + return "socks connect" + case cmdBind: + return "socks bind" + default: + return "socks " + strconv.Itoa(int(cmd)) + } +} + +// An AuthMethod represents a SOCKS authentication method. +type AuthMethod int + +// A Reply represents a SOCKS command reply code. +type Reply int + +func (code Reply) String() string { + switch code { + case StatusSucceeded: + return "succeeded" + case 0x01: + return "general SOCKS server failure" + case 0x02: + return "connection not allowed by ruleset" + case 0x03: + return "network unreachable" + case 0x04: + return "host unreachable" + case 0x05: + return "connection refused" + case 0x06: + return "TTL expired" + case 0x07: + return "command not supported" + case 0x08: + return "address type not supported" + default: + return "unknown code: " + strconv.Itoa(int(code)) + } +} + +// Wire protocol constants. +const ( + Version5 = 0x05 + + AddrTypeIPv4 = 0x01 + AddrTypeFQDN = 0x03 + AddrTypeIPv6 = 0x04 + + CmdConnect Command = 0x01 // establishes an active-open forward proxy connection + cmdBind Command = 0x02 // establishes a passive-open forward proxy connection + + AuthMethodNotRequired AuthMethod = 0x00 // no authentication required + AuthMethodUsernamePassword AuthMethod = 0x02 // use username/password + AuthMethodNoAcceptableMethods AuthMethod = 0xff // no acceptable authentication methods + + StatusSucceeded Reply = 0x00 +) + +// An Addr represents a SOCKS-specific address. +// Either Name or IP is used exclusively. +type Addr struct { + Name string // fully-qualified domain name + IP net.IP + Port int +} + +func (a *Addr) Network() string { return "socks" } + +func (a *Addr) String() string { + if a == nil { + return "" + } + port := strconv.Itoa(a.Port) + if a.IP == nil { + return net.JoinHostPort(a.Name, port) + } + return net.JoinHostPort(a.IP.String(), port) +} + +// A Conn represents a forward proxy connection. +type Conn struct { + net.Conn + + boundAddr net.Addr +} + +// BoundAddr returns the address assigned by the proxy server for +// connecting to the command target address from the proxy server. +func (c *Conn) BoundAddr() net.Addr { + if c == nil { + return nil + } + return c.boundAddr +} + +// A Dialer holds SOCKS-specific options. +type Dialer struct { + cmd Command // either CmdConnect or cmdBind + proxyNetwork string // network between a proxy server and a client + proxyAddress string // proxy server address + + // ProxyDial specifies the optional dial function for + // establishing the transport connection. + ProxyDial func(context.Context, string, string) (net.Conn, error) + + // AuthMethods specifies the list of request authentication + // methods. + // If empty, SOCKS client requests only AuthMethodNotRequired. + AuthMethods []AuthMethod + + // Authenticate specifies the optional authentication + // function. It must be non-nil when AuthMethods is not empty. + // It must return an error when the authentication is failed. + Authenticate func(context.Context, io.ReadWriter, AuthMethod) error +} + +// DialContext connects to the provided address on the provided +// network. +// +// The returned error value may be a net.OpError. When the Op field of +// net.OpError contains "socks", the Source field contains a proxy +// server address and the Addr field contains a command target +// address. +// +// See func Dial of the net package of standard library for a +// description of the network and address parameters. +func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + if err := d.validateTarget(network, address); err != nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + if ctx == nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")} + } + var err error + var c net.Conn + if d.ProxyDial != nil { + c, err = d.ProxyDial(ctx, d.proxyNetwork, d.proxyAddress) + } else { + var dd net.Dialer + c, err = dd.DialContext(ctx, d.proxyNetwork, d.proxyAddress) + } + if err != nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + a, err := d.connect(ctx, c, address) + if err != nil { + c.Close() + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + return &Conn{Conn: c, boundAddr: a}, nil +} + +// DialWithConn initiates a connection from SOCKS server to the target +// network and address using the connection c that is already +// connected to the SOCKS server. +// +// It returns the connection's local address assigned by the SOCKS +// server. +func (d *Dialer) DialWithConn(ctx context.Context, c net.Conn, network, address string) (net.Addr, error) { + if err := d.validateTarget(network, address); err != nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + if ctx == nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")} + } + a, err := d.connect(ctx, c, address) + if err != nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + return a, nil +} + +// Dial connects to the provided address on the provided network. +// +// Unlike DialContext, it returns a raw transport connection instead +// of a forward proxy connection. +// +// Deprecated: Use DialContext or DialWithConn instead. +func (d *Dialer) Dial(network, address string) (net.Conn, error) { + if err := d.validateTarget(network, address); err != nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + var err error + var c net.Conn + if d.ProxyDial != nil { + c, err = d.ProxyDial(context.Background(), d.proxyNetwork, d.proxyAddress) + } else { + c, err = net.Dial(d.proxyNetwork, d.proxyAddress) + } + if err != nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + if _, err := d.DialWithConn(context.Background(), c, network, address); err != nil { + c.Close() + return nil, err + } + return c, nil +} + +func (d *Dialer) validateTarget(network, address string) error { + switch network { + case "tcp", "tcp6", "tcp4": + default: + return errors.New("network not implemented") + } + switch d.cmd { + case CmdConnect, cmdBind: + default: + return errors.New("command not implemented") + } + return nil +} + +func (d *Dialer) pathAddrs(address string) (proxy, dst net.Addr, err error) { + for i, s := range []string{d.proxyAddress, address} { + host, port, err := splitHostPort(s) + if err != nil { + return nil, nil, err + } + a := &Addr{Port: port} + a.IP = net.ParseIP(host) + if a.IP == nil { + a.Name = host + } + if i == 0 { + proxy = a + } else { + dst = a + } + } + return +} + +// NewDialer returns a new Dialer that dials through the provided +// proxy server's network and address. +func NewDialer(network, address string) *Dialer { + return &Dialer{proxyNetwork: network, proxyAddress: address, cmd: CmdConnect} +} + +const ( + authUsernamePasswordVersion = 0x01 + authStatusSucceeded = 0x00 +) + +// UsernamePassword are the credentials for the username/password +// authentication method. +type UsernamePassword struct { + Username string + Password string +} + +// Authenticate authenticates a pair of username and password with the +// proxy server. +func (up *UsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, auth AuthMethod) error { + switch auth { + case AuthMethodNotRequired: + return nil + case AuthMethodUsernamePassword: + if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) == 0 || len(up.Password) > 255 { + return errors.New("invalid username/password") + } + b := []byte{authUsernamePasswordVersion} + b = append(b, byte(len(up.Username))) + b = append(b, up.Username...) + b = append(b, byte(len(up.Password))) + b = append(b, up.Password...) + // TODO(mikio): handle IO deadlines and cancelation if + // necessary + if _, err := rw.Write(b); err != nil { + return err + } + if _, err := io.ReadFull(rw, b[:2]); err != nil { + return err + } + if b[0] != authUsernamePasswordVersion { + return errors.New("invalid username/password version") + } + if b[1] != authStatusSucceeded { + return errors.New("username/password authentication failed") + } + return nil + } + return errors.New("unsupported authentication method " + strconv.Itoa(int(auth))) +} diff --git a/vendor/golang.org/x/net/proxy/dial.go b/vendor/golang.org/x/net/proxy/dial.go new file mode 100644 index 0000000..811c2e4 --- /dev/null +++ b/vendor/golang.org/x/net/proxy/dial.go @@ -0,0 +1,54 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "context" + "net" +) + +// A ContextDialer dials using a context. +type ContextDialer interface { + DialContext(ctx context.Context, network, address string) (net.Conn, error) +} + +// Dial works like DialContext on net.Dialer but using a dialer returned by FromEnvironment. +// +// The passed ctx is only used for returning the Conn, not the lifetime of the Conn. +// +// Custom dialers (registered via RegisterDialerType) that do not implement ContextDialer +// can leak a goroutine for as long as it takes the underlying Dialer implementation to timeout. +// +// A Conn returned from a successful Dial after the context has been cancelled will be immediately closed. +func Dial(ctx context.Context, network, address string) (net.Conn, error) { + d := FromEnvironment() + if xd, ok := d.(ContextDialer); ok { + return xd.DialContext(ctx, network, address) + } + return dialContext(ctx, d, network, address) +} + +// WARNING: this can leak a goroutine for as long as the underlying Dialer implementation takes to timeout +// A Conn returned from a successful Dial after the context has been cancelled will be immediately closed. +func dialContext(ctx context.Context, d Dialer, network, address string) (net.Conn, error) { + var ( + conn net.Conn + done = make(chan struct{}, 1) + err error + ) + go func() { + conn, err = d.Dial(network, address) + close(done) + if conn != nil && ctx.Err() != nil { + conn.Close() + } + }() + select { + case <-ctx.Done(): + err = ctx.Err() + case <-done: + } + return conn, err +} diff --git a/vendor/golang.org/x/net/proxy/direct.go b/vendor/golang.org/x/net/proxy/direct.go new file mode 100644 index 0000000..3d66bde --- /dev/null +++ b/vendor/golang.org/x/net/proxy/direct.go @@ -0,0 +1,31 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "context" + "net" +) + +type direct struct{} + +// Direct implements Dialer by making network connections directly using net.Dial or net.DialContext. +var Direct = direct{} + +var ( + _ Dialer = Direct + _ ContextDialer = Direct +) + +// Dial directly invokes net.Dial with the supplied parameters. +func (direct) Dial(network, addr string) (net.Conn, error) { + return net.Dial(network, addr) +} + +// DialContext instantiates a net.Dialer and invokes its DialContext receiver with the supplied parameters. +func (direct) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, network, addr) +} diff --git a/vendor/golang.org/x/net/proxy/per_host.go b/vendor/golang.org/x/net/proxy/per_host.go new file mode 100644 index 0000000..573fe79 --- /dev/null +++ b/vendor/golang.org/x/net/proxy/per_host.go @@ -0,0 +1,155 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "context" + "net" + "strings" +) + +// A PerHost directs connections to a default Dialer unless the host name +// requested matches one of a number of exceptions. +type PerHost struct { + def, bypass Dialer + + bypassNetworks []*net.IPNet + bypassIPs []net.IP + bypassZones []string + bypassHosts []string +} + +// NewPerHost returns a PerHost Dialer that directs connections to either +// defaultDialer or bypass, depending on whether the connection matches one of +// the configured rules. +func NewPerHost(defaultDialer, bypass Dialer) *PerHost { + return &PerHost{ + def: defaultDialer, + bypass: bypass, + } +} + +// Dial connects to the address addr on the given network through either +// defaultDialer or bypass. +func (p *PerHost) Dial(network, addr string) (c net.Conn, err error) { + host, _, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + + return p.dialerForRequest(host).Dial(network, addr) +} + +// DialContext connects to the address addr on the given network through either +// defaultDialer or bypass. +func (p *PerHost) DialContext(ctx context.Context, network, addr string) (c net.Conn, err error) { + host, _, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + d := p.dialerForRequest(host) + if x, ok := d.(ContextDialer); ok { + return x.DialContext(ctx, network, addr) + } + return dialContext(ctx, d, network, addr) +} + +func (p *PerHost) dialerForRequest(host string) Dialer { + if ip := net.ParseIP(host); ip != nil { + for _, net := range p.bypassNetworks { + if net.Contains(ip) { + return p.bypass + } + } + for _, bypassIP := range p.bypassIPs { + if bypassIP.Equal(ip) { + return p.bypass + } + } + return p.def + } + + for _, zone := range p.bypassZones { + if strings.HasSuffix(host, zone) { + return p.bypass + } + if host == zone[1:] { + // For a zone ".example.com", we match "example.com" + // too. + return p.bypass + } + } + for _, bypassHost := range p.bypassHosts { + if bypassHost == host { + return p.bypass + } + } + return p.def +} + +// AddFromString parses a string that contains comma-separated values +// specifying hosts that should use the bypass proxy. Each value is either an +// IP address, a CIDR range, a zone (*.example.com) or a host name +// (localhost). A best effort is made to parse the string and errors are +// ignored. +func (p *PerHost) AddFromString(s string) { + hosts := strings.Split(s, ",") + for _, host := range hosts { + host = strings.TrimSpace(host) + if len(host) == 0 { + continue + } + if strings.Contains(host, "/") { + // We assume that it's a CIDR address like 127.0.0.0/8 + if _, net, err := net.ParseCIDR(host); err == nil { + p.AddNetwork(net) + } + continue + } + if ip := net.ParseIP(host); ip != nil { + p.AddIP(ip) + continue + } + if strings.HasPrefix(host, "*.") { + p.AddZone(host[1:]) + continue + } + p.AddHost(host) + } +} + +// AddIP specifies an IP address that will use the bypass proxy. Note that +// this will only take effect if a literal IP address is dialed. A connection +// to a named host will never match an IP. +func (p *PerHost) AddIP(ip net.IP) { + p.bypassIPs = append(p.bypassIPs, ip) +} + +// AddNetwork specifies an IP range that will use the bypass proxy. Note that +// this will only take effect if a literal IP address is dialed. A connection +// to a named host will never match. +func (p *PerHost) AddNetwork(net *net.IPNet) { + p.bypassNetworks = append(p.bypassNetworks, net) +} + +// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of +// "example.com" matches "example.com" and all of its subdomains. +func (p *PerHost) AddZone(zone string) { + if strings.HasSuffix(zone, ".") { + zone = zone[:len(zone)-1] + } + if !strings.HasPrefix(zone, ".") { + zone = "." + zone + } + p.bypassZones = append(p.bypassZones, zone) +} + +// AddHost specifies a host name that will use the bypass proxy. +func (p *PerHost) AddHost(host string) { + if strings.HasSuffix(host, ".") { + host = host[:len(host)-1] + } + p.bypassHosts = append(p.bypassHosts, host) +} diff --git a/vendor/golang.org/x/net/proxy/proxy.go b/vendor/golang.org/x/net/proxy/proxy.go new file mode 100644 index 0000000..9ff4b9a --- /dev/null +++ b/vendor/golang.org/x/net/proxy/proxy.go @@ -0,0 +1,149 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package proxy provides support for a variety of protocols to proxy network +// data. +package proxy // import "golang.org/x/net/proxy" + +import ( + "errors" + "net" + "net/url" + "os" + "sync" +) + +// A Dialer is a means to establish a connection. +// Custom dialers should also implement ContextDialer. +type Dialer interface { + // Dial connects to the given address via the proxy. + Dial(network, addr string) (c net.Conn, err error) +} + +// Auth contains authentication parameters that specific Dialers may require. +type Auth struct { + User, Password string +} + +// FromEnvironment returns the dialer specified by the proxy-related +// variables in the environment and makes underlying connections +// directly. +func FromEnvironment() Dialer { + return FromEnvironmentUsing(Direct) +} + +// FromEnvironmentUsing returns the dialer specify by the proxy-related +// variables in the environment and makes underlying connections +// using the provided forwarding Dialer (for instance, a *net.Dialer +// with desired configuration). +func FromEnvironmentUsing(forward Dialer) Dialer { + allProxy := allProxyEnv.Get() + if len(allProxy) == 0 { + return forward + } + + proxyURL, err := url.Parse(allProxy) + if err != nil { + return forward + } + proxy, err := FromURL(proxyURL, forward) + if err != nil { + return forward + } + + noProxy := noProxyEnv.Get() + if len(noProxy) == 0 { + return proxy + } + + perHost := NewPerHost(proxy, forward) + perHost.AddFromString(noProxy) + return perHost +} + +// proxySchemes is a map from URL schemes to a function that creates a Dialer +// from a URL with such a scheme. +var proxySchemes map[string]func(*url.URL, Dialer) (Dialer, error) + +// RegisterDialerType takes a URL scheme and a function to generate Dialers from +// a URL with that scheme and a forwarding Dialer. Registered schemes are used +// by FromURL. +func RegisterDialerType(scheme string, f func(*url.URL, Dialer) (Dialer, error)) { + if proxySchemes == nil { + proxySchemes = make(map[string]func(*url.URL, Dialer) (Dialer, error)) + } + proxySchemes[scheme] = f +} + +// FromURL returns a Dialer given a URL specification and an underlying +// Dialer for it to make network requests. +func FromURL(u *url.URL, forward Dialer) (Dialer, error) { + var auth *Auth + if u.User != nil { + auth = new(Auth) + auth.User = u.User.Username() + if p, ok := u.User.Password(); ok { + auth.Password = p + } + } + + switch u.Scheme { + case "socks5", "socks5h": + addr := u.Hostname() + port := u.Port() + if port == "" { + port = "1080" + } + return SOCKS5("tcp", net.JoinHostPort(addr, port), auth, forward) + } + + // If the scheme doesn't match any of the built-in schemes, see if it + // was registered by another package. + if proxySchemes != nil { + if f, ok := proxySchemes[u.Scheme]; ok { + return f(u, forward) + } + } + + return nil, errors.New("proxy: unknown scheme: " + u.Scheme) +} + +var ( + allProxyEnv = &envOnce{ + names: []string{"ALL_PROXY", "all_proxy"}, + } + noProxyEnv = &envOnce{ + names: []string{"NO_PROXY", "no_proxy"}, + } +) + +// envOnce looks up an environment variable (optionally by multiple +// names) once. It mitigates expensive lookups on some platforms +// (e.g. Windows). +// (Borrowed from net/http/transport.go) +type envOnce struct { + names []string + once sync.Once + val string +} + +func (e *envOnce) Get() string { + e.once.Do(e.init) + return e.val +} + +func (e *envOnce) init() { + for _, n := range e.names { + e.val = os.Getenv(n) + if e.val != "" { + return + } + } +} + +// reset is used by tests +func (e *envOnce) reset() { + e.once = sync.Once{} + e.val = "" +} diff --git a/vendor/golang.org/x/net/proxy/socks5.go b/vendor/golang.org/x/net/proxy/socks5.go new file mode 100644 index 0000000..c91651f --- /dev/null +++ b/vendor/golang.org/x/net/proxy/socks5.go @@ -0,0 +1,42 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "context" + "net" + + "golang.org/x/net/internal/socks" +) + +// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given +// address with an optional username and password. +// See RFC 1928 and RFC 1929. +func SOCKS5(network, address string, auth *Auth, forward Dialer) (Dialer, error) { + d := socks.NewDialer(network, address) + if forward != nil { + if f, ok := forward.(ContextDialer); ok { + d.ProxyDial = func(ctx context.Context, network string, address string) (net.Conn, error) { + return f.DialContext(ctx, network, address) + } + } else { + d.ProxyDial = func(ctx context.Context, network string, address string) (net.Conn, error) { + return dialContext(ctx, forward, network, address) + } + } + } + if auth != nil { + up := socks.UsernamePassword{ + Username: auth.User, + Password: auth.Password, + } + d.AuthMethods = []socks.AuthMethod{ + socks.AuthMethodNotRequired, + socks.AuthMethodUsernamePassword, + } + d.Authenticate = up.Authenticate + } + return d, nil +} diff --git a/vendor/golang.org/x/net/websocket/client.go b/vendor/golang.org/x/net/websocket/client.go new file mode 100644 index 0000000..69a4ac7 --- /dev/null +++ b/vendor/golang.org/x/net/websocket/client.go @@ -0,0 +1,106 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bufio" + "io" + "net" + "net/http" + "net/url" +) + +// DialError is an error that occurs while dialling a websocket server. +type DialError struct { + *Config + Err error +} + +func (e *DialError) Error() string { + return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error() +} + +// NewConfig creates a new WebSocket config for client connection. +func NewConfig(server, origin string) (config *Config, err error) { + config = new(Config) + config.Version = ProtocolVersionHybi13 + config.Location, err = url.ParseRequestURI(server) + if err != nil { + return + } + config.Origin, err = url.ParseRequestURI(origin) + if err != nil { + return + } + config.Header = http.Header(make(map[string][]string)) + return +} + +// NewClient creates a new WebSocket client connection over rwc. +func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) { + br := bufio.NewReader(rwc) + bw := bufio.NewWriter(rwc) + err = hybiClientHandshake(config, br, bw) + if err != nil { + return + } + buf := bufio.NewReadWriter(br, bw) + ws = newHybiClientConn(config, buf, rwc) + return +} + +// Dial opens a new client connection to a WebSocket. +func Dial(url_, protocol, origin string) (ws *Conn, err error) { + config, err := NewConfig(url_, origin) + if err != nil { + return nil, err + } + if protocol != "" { + config.Protocol = []string{protocol} + } + return DialConfig(config) +} + +var portMap = map[string]string{ + "ws": "80", + "wss": "443", +} + +func parseAuthority(location *url.URL) string { + if _, ok := portMap[location.Scheme]; ok { + if _, _, err := net.SplitHostPort(location.Host); err != nil { + return net.JoinHostPort(location.Host, portMap[location.Scheme]) + } + } + return location.Host +} + +// DialConfig opens a new client connection to a WebSocket with a config. +func DialConfig(config *Config) (ws *Conn, err error) { + var client net.Conn + if config.Location == nil { + return nil, &DialError{config, ErrBadWebSocketLocation} + } + if config.Origin == nil { + return nil, &DialError{config, ErrBadWebSocketOrigin} + } + dialer := config.Dialer + if dialer == nil { + dialer = &net.Dialer{} + } + client, err = dialWithDialer(dialer, config) + if err != nil { + goto Error + } + ws, err = NewClient(config, client) + if err != nil { + client.Close() + goto Error + } + return + +Error: + return nil, &DialError{config, err} +} diff --git a/vendor/golang.org/x/net/websocket/dial.go b/vendor/golang.org/x/net/websocket/dial.go new file mode 100644 index 0000000..2dab943 --- /dev/null +++ b/vendor/golang.org/x/net/websocket/dial.go @@ -0,0 +1,24 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "crypto/tls" + "net" +) + +func dialWithDialer(dialer *net.Dialer, config *Config) (conn net.Conn, err error) { + switch config.Location.Scheme { + case "ws": + conn, err = dialer.Dial("tcp", parseAuthority(config.Location)) + + case "wss": + conn, err = tls.DialWithDialer(dialer, "tcp", parseAuthority(config.Location), config.TlsConfig) + + default: + err = ErrBadScheme + } + return +} diff --git a/vendor/golang.org/x/net/websocket/hybi.go b/vendor/golang.org/x/net/websocket/hybi.go new file mode 100644 index 0000000..8cffdd1 --- /dev/null +++ b/vendor/golang.org/x/net/websocket/hybi.go @@ -0,0 +1,583 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +// This file implements a protocol of hybi draft. +// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17 + +import ( + "bufio" + "bytes" + "crypto/rand" + "crypto/sha1" + "encoding/base64" + "encoding/binary" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +const ( + websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + + closeStatusNormal = 1000 + closeStatusGoingAway = 1001 + closeStatusProtocolError = 1002 + closeStatusUnsupportedData = 1003 + closeStatusFrameTooLarge = 1004 + closeStatusNoStatusRcvd = 1005 + closeStatusAbnormalClosure = 1006 + closeStatusBadMessageData = 1007 + closeStatusPolicyViolation = 1008 + closeStatusTooBigData = 1009 + closeStatusExtensionMismatch = 1010 + + maxControlFramePayloadLength = 125 +) + +var ( + ErrBadMaskingKey = &ProtocolError{"bad masking key"} + ErrBadPongMessage = &ProtocolError{"bad pong message"} + ErrBadClosingStatus = &ProtocolError{"bad closing status"} + ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"} + ErrNotImplemented = &ProtocolError{"not implemented"} + + handshakeHeader = map[string]bool{ + "Host": true, + "Upgrade": true, + "Connection": true, + "Sec-Websocket-Key": true, + "Sec-Websocket-Origin": true, + "Sec-Websocket-Version": true, + "Sec-Websocket-Protocol": true, + "Sec-Websocket-Accept": true, + } +) + +// A hybiFrameHeader is a frame header as defined in hybi draft. +type hybiFrameHeader struct { + Fin bool + Rsv [3]bool + OpCode byte + Length int64 + MaskingKey []byte + + data *bytes.Buffer +} + +// A hybiFrameReader is a reader for hybi frame. +type hybiFrameReader struct { + reader io.Reader + + header hybiFrameHeader + pos int64 + length int +} + +func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) { + n, err = frame.reader.Read(msg) + if frame.header.MaskingKey != nil { + for i := 0; i < n; i++ { + msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4] + frame.pos++ + } + } + return n, err +} + +func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode } + +func (frame *hybiFrameReader) HeaderReader() io.Reader { + if frame.header.data == nil { + return nil + } + if frame.header.data.Len() == 0 { + return nil + } + return frame.header.data +} + +func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil } + +func (frame *hybiFrameReader) Len() (n int) { return frame.length } + +// A hybiFrameReaderFactory creates new frame reader based on its frame type. +type hybiFrameReaderFactory struct { + *bufio.Reader +} + +// NewFrameReader reads a frame header from the connection, and creates new reader for the frame. +// See Section 5.2 Base Framing protocol for detail. +// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2 +func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) { + hybiFrame := new(hybiFrameReader) + frame = hybiFrame + var header []byte + var b byte + // First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits) + b, err = buf.ReadByte() + if err != nil { + return + } + header = append(header, b) + hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0 + for i := 0; i < 3; i++ { + j := uint(6 - i) + hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0 + } + hybiFrame.header.OpCode = header[0] & 0x0f + + // Second byte. Mask/Payload len(7bits) + b, err = buf.ReadByte() + if err != nil { + return + } + header = append(header, b) + mask := (b & 0x80) != 0 + b &= 0x7f + lengthFields := 0 + switch { + case b <= 125: // Payload length 7bits. + hybiFrame.header.Length = int64(b) + case b == 126: // Payload length 7+16bits + lengthFields = 2 + case b == 127: // Payload length 7+64bits + lengthFields = 8 + } + for i := 0; i < lengthFields; i++ { + b, err = buf.ReadByte() + if err != nil { + return + } + if lengthFields == 8 && i == 0 { // MSB must be zero when 7+64 bits + b &= 0x7f + } + header = append(header, b) + hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b) + } + if mask { + // Masking key. 4 bytes. + for i := 0; i < 4; i++ { + b, err = buf.ReadByte() + if err != nil { + return + } + header = append(header, b) + hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b) + } + } + hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length) + hybiFrame.header.data = bytes.NewBuffer(header) + hybiFrame.length = len(header) + int(hybiFrame.header.Length) + return +} + +// A HybiFrameWriter is a writer for hybi frame. +type hybiFrameWriter struct { + writer *bufio.Writer + + header *hybiFrameHeader +} + +func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) { + var header []byte + var b byte + if frame.header.Fin { + b |= 0x80 + } + for i := 0; i < 3; i++ { + if frame.header.Rsv[i] { + j := uint(6 - i) + b |= 1 << j + } + } + b |= frame.header.OpCode + header = append(header, b) + if frame.header.MaskingKey != nil { + b = 0x80 + } else { + b = 0 + } + lengthFields := 0 + length := len(msg) + switch { + case length <= 125: + b |= byte(length) + case length < 65536: + b |= 126 + lengthFields = 2 + default: + b |= 127 + lengthFields = 8 + } + header = append(header, b) + for i := 0; i < lengthFields; i++ { + j := uint((lengthFields - i - 1) * 8) + b = byte((length >> j) & 0xff) + header = append(header, b) + } + if frame.header.MaskingKey != nil { + if len(frame.header.MaskingKey) != 4 { + return 0, ErrBadMaskingKey + } + header = append(header, frame.header.MaskingKey...) + frame.writer.Write(header) + data := make([]byte, length) + for i := range data { + data[i] = msg[i] ^ frame.header.MaskingKey[i%4] + } + frame.writer.Write(data) + err = frame.writer.Flush() + return length, err + } + frame.writer.Write(header) + frame.writer.Write(msg) + err = frame.writer.Flush() + return length, err +} + +func (frame *hybiFrameWriter) Close() error { return nil } + +type hybiFrameWriterFactory struct { + *bufio.Writer + needMaskingKey bool +} + +func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) { + frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType} + if buf.needMaskingKey { + frameHeader.MaskingKey, err = generateMaskingKey() + if err != nil { + return nil, err + } + } + return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil +} + +type hybiFrameHandler struct { + conn *Conn + payloadType byte +} + +func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (frameReader, error) { + if handler.conn.IsServerConn() { + // The client MUST mask all frames sent to the server. + if frame.(*hybiFrameReader).header.MaskingKey == nil { + handler.WriteClose(closeStatusProtocolError) + return nil, io.EOF + } + } else { + // The server MUST NOT mask all frames. + if frame.(*hybiFrameReader).header.MaskingKey != nil { + handler.WriteClose(closeStatusProtocolError) + return nil, io.EOF + } + } + if header := frame.HeaderReader(); header != nil { + io.Copy(ioutil.Discard, header) + } + switch frame.PayloadType() { + case ContinuationFrame: + frame.(*hybiFrameReader).header.OpCode = handler.payloadType + case TextFrame, BinaryFrame: + handler.payloadType = frame.PayloadType() + case CloseFrame: + return nil, io.EOF + case PingFrame, PongFrame: + b := make([]byte, maxControlFramePayloadLength) + n, err := io.ReadFull(frame, b) + if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF { + return nil, err + } + io.Copy(ioutil.Discard, frame) + if frame.PayloadType() == PingFrame { + if _, err := handler.WritePong(b[:n]); err != nil { + return nil, err + } + } + return nil, nil + } + return frame, nil +} + +func (handler *hybiFrameHandler) WriteClose(status int) (err error) { + handler.conn.wio.Lock() + defer handler.conn.wio.Unlock() + w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame) + if err != nil { + return err + } + msg := make([]byte, 2) + binary.BigEndian.PutUint16(msg, uint16(status)) + _, err = w.Write(msg) + w.Close() + return err +} + +func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) { + handler.conn.wio.Lock() + defer handler.conn.wio.Unlock() + w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame) + if err != nil { + return 0, err + } + n, err = w.Write(msg) + w.Close() + return n, err +} + +// newHybiConn creates a new WebSocket connection speaking hybi draft protocol. +func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn { + if buf == nil { + br := bufio.NewReader(rwc) + bw := bufio.NewWriter(rwc) + buf = bufio.NewReadWriter(br, bw) + } + ws := &Conn{config: config, request: request, buf: buf, rwc: rwc, + frameReaderFactory: hybiFrameReaderFactory{buf.Reader}, + frameWriterFactory: hybiFrameWriterFactory{ + buf.Writer, request == nil}, + PayloadType: TextFrame, + defaultCloseStatus: closeStatusNormal} + ws.frameHandler = &hybiFrameHandler{conn: ws} + return ws +} + +// generateMaskingKey generates a masking key for a frame. +func generateMaskingKey() (maskingKey []byte, err error) { + maskingKey = make([]byte, 4) + if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil { + return + } + return +} + +// generateNonce generates a nonce consisting of a randomly selected 16-byte +// value that has been base64-encoded. +func generateNonce() (nonce []byte) { + key := make([]byte, 16) + if _, err := io.ReadFull(rand.Reader, key); err != nil { + panic(err) + } + nonce = make([]byte, 24) + base64.StdEncoding.Encode(nonce, key) + return +} + +// removeZone removes IPv6 zone identifer from host. +// E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080" +func removeZone(host string) string { + if !strings.HasPrefix(host, "[") { + return host + } + i := strings.LastIndex(host, "]") + if i < 0 { + return host + } + j := strings.LastIndex(host[:i], "%") + if j < 0 { + return host + } + return host[:j] + host[i:] +} + +// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of +// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string. +func getNonceAccept(nonce []byte) (expected []byte, err error) { + h := sha1.New() + if _, err = h.Write(nonce); err != nil { + return + } + if _, err = h.Write([]byte(websocketGUID)); err != nil { + return + } + expected = make([]byte, 28) + base64.StdEncoding.Encode(expected, h.Sum(nil)) + return +} + +// Client handshake described in draft-ietf-hybi-thewebsocket-protocol-17 +func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) { + bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n") + + // According to RFC 6874, an HTTP client, proxy, or other + // intermediary must remove any IPv6 zone identifier attached + // to an outgoing URI. + bw.WriteString("Host: " + removeZone(config.Location.Host) + "\r\n") + bw.WriteString("Upgrade: websocket\r\n") + bw.WriteString("Connection: Upgrade\r\n") + nonce := generateNonce() + if config.handshakeData != nil { + nonce = []byte(config.handshakeData["key"]) + } + bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n") + bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n") + + if config.Version != ProtocolVersionHybi13 { + return ErrBadProtocolVersion + } + + bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n") + if len(config.Protocol) > 0 { + bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n") + } + // TODO(ukai): send Sec-WebSocket-Extensions. + err = config.Header.WriteSubset(bw, handshakeHeader) + if err != nil { + return err + } + + bw.WriteString("\r\n") + if err = bw.Flush(); err != nil { + return err + } + + resp, err := http.ReadResponse(br, &http.Request{Method: "GET"}) + if err != nil { + return err + } + if resp.StatusCode != 101 { + return ErrBadStatus + } + if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" || + strings.ToLower(resp.Header.Get("Connection")) != "upgrade" { + return ErrBadUpgrade + } + expectedAccept, err := getNonceAccept(nonce) + if err != nil { + return err + } + if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) { + return ErrChallengeResponse + } + if resp.Header.Get("Sec-WebSocket-Extensions") != "" { + return ErrUnsupportedExtensions + } + offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol") + if offeredProtocol != "" { + protocolMatched := false + for i := 0; i < len(config.Protocol); i++ { + if config.Protocol[i] == offeredProtocol { + protocolMatched = true + break + } + } + if !protocolMatched { + return ErrBadWebSocketProtocol + } + config.Protocol = []string{offeredProtocol} + } + + return nil +} + +// newHybiClientConn creates a client WebSocket connection after handshake. +func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn { + return newHybiConn(config, buf, rwc, nil) +} + +// A HybiServerHandshaker performs a server handshake using hybi draft protocol. +type hybiServerHandshaker struct { + *Config + accept []byte +} + +func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) { + c.Version = ProtocolVersionHybi13 + if req.Method != "GET" { + return http.StatusMethodNotAllowed, ErrBadRequestMethod + } + // HTTP version can be safely ignored. + + if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" || + !strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") { + return http.StatusBadRequest, ErrNotWebSocket + } + + key := req.Header.Get("Sec-Websocket-Key") + if key == "" { + return http.StatusBadRequest, ErrChallengeResponse + } + version := req.Header.Get("Sec-Websocket-Version") + switch version { + case "13": + c.Version = ProtocolVersionHybi13 + default: + return http.StatusBadRequest, ErrBadWebSocketVersion + } + var scheme string + if req.TLS != nil { + scheme = "wss" + } else { + scheme = "ws" + } + c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI()) + if err != nil { + return http.StatusBadRequest, err + } + protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol")) + if protocol != "" { + protocols := strings.Split(protocol, ",") + for i := 0; i < len(protocols); i++ { + c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i])) + } + } + c.accept, err = getNonceAccept([]byte(key)) + if err != nil { + return http.StatusInternalServerError, err + } + return http.StatusSwitchingProtocols, nil +} + +// Origin parses the Origin header in req. +// If the Origin header is not set, it returns nil and nil. +func Origin(config *Config, req *http.Request) (*url.URL, error) { + var origin string + switch config.Version { + case ProtocolVersionHybi13: + origin = req.Header.Get("Origin") + } + if origin == "" { + return nil, nil + } + return url.ParseRequestURI(origin) +} + +func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) { + if len(c.Protocol) > 0 { + if len(c.Protocol) != 1 { + // You need choose a Protocol in Handshake func in Server. + return ErrBadWebSocketProtocol + } + } + buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n") + buf.WriteString("Upgrade: websocket\r\n") + buf.WriteString("Connection: Upgrade\r\n") + buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n") + if len(c.Protocol) > 0 { + buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n") + } + // TODO(ukai): send Sec-WebSocket-Extensions. + if c.Header != nil { + err := c.Header.WriteSubset(buf, handshakeHeader) + if err != nil { + return err + } + } + buf.WriteString("\r\n") + return buf.Flush() +} + +func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn { + return newHybiServerConn(c.Config, buf, rwc, request) +} + +// newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol. +func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn { + return newHybiConn(config, buf, rwc, request) +} diff --git a/vendor/golang.org/x/net/websocket/server.go b/vendor/golang.org/x/net/websocket/server.go new file mode 100644 index 0000000..0895dea --- /dev/null +++ b/vendor/golang.org/x/net/websocket/server.go @@ -0,0 +1,113 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bufio" + "fmt" + "io" + "net/http" +) + +func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request, config *Config, handshake func(*Config, *http.Request) error) (conn *Conn, err error) { + var hs serverHandshaker = &hybiServerHandshaker{Config: config} + code, err := hs.ReadHandshake(buf.Reader, req) + if err == ErrBadWebSocketVersion { + fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) + fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion) + buf.WriteString("\r\n") + buf.WriteString(err.Error()) + buf.Flush() + return + } + if err != nil { + fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) + buf.WriteString("\r\n") + buf.WriteString(err.Error()) + buf.Flush() + return + } + if handshake != nil { + err = handshake(config, req) + if err != nil { + code = http.StatusForbidden + fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) + buf.WriteString("\r\n") + buf.Flush() + return + } + } + err = hs.AcceptHandshake(buf.Writer) + if err != nil { + code = http.StatusBadRequest + fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) + buf.WriteString("\r\n") + buf.Flush() + return + } + conn = hs.NewServerConn(buf, rwc, req) + return +} + +// Server represents a server of a WebSocket. +type Server struct { + // Config is a WebSocket configuration for new WebSocket connection. + Config + + // Handshake is an optional function in WebSocket handshake. + // For example, you can check, or don't check Origin header. + // Another example, you can select config.Protocol. + Handshake func(*Config, *http.Request) error + + // Handler handles a WebSocket connection. + Handler +} + +// ServeHTTP implements the http.Handler interface for a WebSocket +func (s Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { + s.serveWebSocket(w, req) +} + +func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) { + rwc, buf, err := w.(http.Hijacker).Hijack() + if err != nil { + panic("Hijack failed: " + err.Error()) + } + // The server should abort the WebSocket connection if it finds + // the client did not send a handshake that matches with protocol + // specification. + defer rwc.Close() + conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake) + if err != nil { + return + } + if conn == nil { + panic("unexpected nil conn") + } + s.Handler(conn) +} + +// Handler is a simple interface to a WebSocket browser client. +// It checks if Origin header is valid URL by default. +// You might want to verify websocket.Conn.Config().Origin in the func. +// If you use Server instead of Handler, you could call websocket.Origin and +// check the origin in your Handshake func. So, if you want to accept +// non-browser clients, which do not send an Origin header, set a +// Server.Handshake that does not check the origin. +type Handler func(*Conn) + +func checkOrigin(config *Config, req *http.Request) (err error) { + config.Origin, err = Origin(config, req) + if err == nil && config.Origin == nil { + return fmt.Errorf("null origin") + } + return err +} + +// ServeHTTP implements the http.Handler interface for a WebSocket +func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + s := Server{Handler: h, Handshake: checkOrigin} + s.serveWebSocket(w, req) +} diff --git a/vendor/golang.org/x/net/websocket/websocket.go b/vendor/golang.org/x/net/websocket/websocket.go new file mode 100644 index 0000000..6c45c73 --- /dev/null +++ b/vendor/golang.org/x/net/websocket/websocket.go @@ -0,0 +1,451 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package websocket implements a client and server for the WebSocket protocol +// as specified in RFC 6455. +// +// This package currently lacks some features found in alternative +// and more actively maintained WebSocket packages: +// +// https://godoc.org/github.com/gorilla/websocket +// https://godoc.org/nhooyr.io/websocket +package websocket // import "golang.org/x/net/websocket" + +import ( + "bufio" + "crypto/tls" + "encoding/json" + "errors" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "sync" + "time" +) + +const ( + ProtocolVersionHybi13 = 13 + ProtocolVersionHybi = ProtocolVersionHybi13 + SupportedProtocolVersion = "13" + + ContinuationFrame = 0 + TextFrame = 1 + BinaryFrame = 2 + CloseFrame = 8 + PingFrame = 9 + PongFrame = 10 + UnknownFrame = 255 + + DefaultMaxPayloadBytes = 32 << 20 // 32MB +) + +// ProtocolError represents WebSocket protocol errors. +type ProtocolError struct { + ErrorString string +} + +func (err *ProtocolError) Error() string { return err.ErrorString } + +var ( + ErrBadProtocolVersion = &ProtocolError{"bad protocol version"} + ErrBadScheme = &ProtocolError{"bad scheme"} + ErrBadStatus = &ProtocolError{"bad status"} + ErrBadUpgrade = &ProtocolError{"missing or bad upgrade"} + ErrBadWebSocketOrigin = &ProtocolError{"missing or bad WebSocket-Origin"} + ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"} + ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"} + ErrBadWebSocketVersion = &ProtocolError{"missing or bad WebSocket Version"} + ErrChallengeResponse = &ProtocolError{"mismatch challenge/response"} + ErrBadFrame = &ProtocolError{"bad frame"} + ErrBadFrameBoundary = &ProtocolError{"not on frame boundary"} + ErrNotWebSocket = &ProtocolError{"not websocket protocol"} + ErrBadRequestMethod = &ProtocolError{"bad method"} + ErrNotSupported = &ProtocolError{"not supported"} +) + +// ErrFrameTooLarge is returned by Codec's Receive method if payload size +// exceeds limit set by Conn.MaxPayloadBytes +var ErrFrameTooLarge = errors.New("websocket: frame payload size exceeds limit") + +// Addr is an implementation of net.Addr for WebSocket. +type Addr struct { + *url.URL +} + +// Network returns the network type for a WebSocket, "websocket". +func (addr *Addr) Network() string { return "websocket" } + +// Config is a WebSocket configuration +type Config struct { + // A WebSocket server address. + Location *url.URL + + // A Websocket client origin. + Origin *url.URL + + // WebSocket subprotocols. + Protocol []string + + // WebSocket protocol version. + Version int + + // TLS config for secure WebSocket (wss). + TlsConfig *tls.Config + + // Additional header fields to be sent in WebSocket opening handshake. + Header http.Header + + // Dialer used when opening websocket connections. + Dialer *net.Dialer + + handshakeData map[string]string +} + +// serverHandshaker is an interface to handle WebSocket server side handshake. +type serverHandshaker interface { + // ReadHandshake reads handshake request message from client. + // Returns http response code and error if any. + ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) + + // AcceptHandshake accepts the client handshake request and sends + // handshake response back to client. + AcceptHandshake(buf *bufio.Writer) (err error) + + // NewServerConn creates a new WebSocket connection. + NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn) +} + +// frameReader is an interface to read a WebSocket frame. +type frameReader interface { + // Reader is to read payload of the frame. + io.Reader + + // PayloadType returns payload type. + PayloadType() byte + + // HeaderReader returns a reader to read header of the frame. + HeaderReader() io.Reader + + // TrailerReader returns a reader to read trailer of the frame. + // If it returns nil, there is no trailer in the frame. + TrailerReader() io.Reader + + // Len returns total length of the frame, including header and trailer. + Len() int +} + +// frameReaderFactory is an interface to creates new frame reader. +type frameReaderFactory interface { + NewFrameReader() (r frameReader, err error) +} + +// frameWriter is an interface to write a WebSocket frame. +type frameWriter interface { + // Writer is to write payload of the frame. + io.WriteCloser +} + +// frameWriterFactory is an interface to create new frame writer. +type frameWriterFactory interface { + NewFrameWriter(payloadType byte) (w frameWriter, err error) +} + +type frameHandler interface { + HandleFrame(frame frameReader) (r frameReader, err error) + WriteClose(status int) (err error) +} + +// Conn represents a WebSocket connection. +// +// Multiple goroutines may invoke methods on a Conn simultaneously. +type Conn struct { + config *Config + request *http.Request + + buf *bufio.ReadWriter + rwc io.ReadWriteCloser + + rio sync.Mutex + frameReaderFactory + frameReader + + wio sync.Mutex + frameWriterFactory + + frameHandler + PayloadType byte + defaultCloseStatus int + + // MaxPayloadBytes limits the size of frame payload received over Conn + // by Codec's Receive method. If zero, DefaultMaxPayloadBytes is used. + MaxPayloadBytes int +} + +// Read implements the io.Reader interface: +// it reads data of a frame from the WebSocket connection. +// if msg is not large enough for the frame data, it fills the msg and next Read +// will read the rest of the frame data. +// it reads Text frame or Binary frame. +func (ws *Conn) Read(msg []byte) (n int, err error) { + ws.rio.Lock() + defer ws.rio.Unlock() +again: + if ws.frameReader == nil { + frame, err := ws.frameReaderFactory.NewFrameReader() + if err != nil { + return 0, err + } + ws.frameReader, err = ws.frameHandler.HandleFrame(frame) + if err != nil { + return 0, err + } + if ws.frameReader == nil { + goto again + } + } + n, err = ws.frameReader.Read(msg) + if err == io.EOF { + if trailer := ws.frameReader.TrailerReader(); trailer != nil { + io.Copy(ioutil.Discard, trailer) + } + ws.frameReader = nil + goto again + } + return n, err +} + +// Write implements the io.Writer interface: +// it writes data as a frame to the WebSocket connection. +func (ws *Conn) Write(msg []byte) (n int, err error) { + ws.wio.Lock() + defer ws.wio.Unlock() + w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType) + if err != nil { + return 0, err + } + n, err = w.Write(msg) + w.Close() + return n, err +} + +// Close implements the io.Closer interface. +func (ws *Conn) Close() error { + err := ws.frameHandler.WriteClose(ws.defaultCloseStatus) + err1 := ws.rwc.Close() + if err != nil { + return err + } + return err1 +} + +// IsClientConn reports whether ws is a client-side connection. +func (ws *Conn) IsClientConn() bool { return ws.request == nil } + +// IsServerConn reports whether ws is a server-side connection. +func (ws *Conn) IsServerConn() bool { return ws.request != nil } + +// LocalAddr returns the WebSocket Origin for the connection for client, or +// the WebSocket location for server. +func (ws *Conn) LocalAddr() net.Addr { + if ws.IsClientConn() { + return &Addr{ws.config.Origin} + } + return &Addr{ws.config.Location} +} + +// RemoteAddr returns the WebSocket location for the connection for client, or +// the Websocket Origin for server. +func (ws *Conn) RemoteAddr() net.Addr { + if ws.IsClientConn() { + return &Addr{ws.config.Location} + } + return &Addr{ws.config.Origin} +} + +var errSetDeadline = errors.New("websocket: cannot set deadline: not using a net.Conn") + +// SetDeadline sets the connection's network read & write deadlines. +func (ws *Conn) SetDeadline(t time.Time) error { + if conn, ok := ws.rwc.(net.Conn); ok { + return conn.SetDeadline(t) + } + return errSetDeadline +} + +// SetReadDeadline sets the connection's network read deadline. +func (ws *Conn) SetReadDeadline(t time.Time) error { + if conn, ok := ws.rwc.(net.Conn); ok { + return conn.SetReadDeadline(t) + } + return errSetDeadline +} + +// SetWriteDeadline sets the connection's network write deadline. +func (ws *Conn) SetWriteDeadline(t time.Time) error { + if conn, ok := ws.rwc.(net.Conn); ok { + return conn.SetWriteDeadline(t) + } + return errSetDeadline +} + +// Config returns the WebSocket config. +func (ws *Conn) Config() *Config { return ws.config } + +// Request returns the http request upgraded to the WebSocket. +// It is nil for client side. +func (ws *Conn) Request() *http.Request { return ws.request } + +// Codec represents a symmetric pair of functions that implement a codec. +type Codec struct { + Marshal func(v interface{}) (data []byte, payloadType byte, err error) + Unmarshal func(data []byte, payloadType byte, v interface{}) (err error) +} + +// Send sends v marshaled by cd.Marshal as single frame to ws. +func (cd Codec) Send(ws *Conn, v interface{}) (err error) { + data, payloadType, err := cd.Marshal(v) + if err != nil { + return err + } + ws.wio.Lock() + defer ws.wio.Unlock() + w, err := ws.frameWriterFactory.NewFrameWriter(payloadType) + if err != nil { + return err + } + _, err = w.Write(data) + w.Close() + return err +} + +// Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores +// in v. The whole frame payload is read to an in-memory buffer; max size of +// payload is defined by ws.MaxPayloadBytes. If frame payload size exceeds +// limit, ErrFrameTooLarge is returned; in this case frame is not read off wire +// completely. The next call to Receive would read and discard leftover data of +// previous oversized frame before processing next frame. +func (cd Codec) Receive(ws *Conn, v interface{}) (err error) { + ws.rio.Lock() + defer ws.rio.Unlock() + if ws.frameReader != nil { + _, err = io.Copy(ioutil.Discard, ws.frameReader) + if err != nil { + return err + } + ws.frameReader = nil + } +again: + frame, err := ws.frameReaderFactory.NewFrameReader() + if err != nil { + return err + } + frame, err = ws.frameHandler.HandleFrame(frame) + if err != nil { + return err + } + if frame == nil { + goto again + } + maxPayloadBytes := ws.MaxPayloadBytes + if maxPayloadBytes == 0 { + maxPayloadBytes = DefaultMaxPayloadBytes + } + if hf, ok := frame.(*hybiFrameReader); ok && hf.header.Length > int64(maxPayloadBytes) { + // payload size exceeds limit, no need to call Unmarshal + // + // set frameReader to current oversized frame so that + // the next call to this function can drain leftover + // data before processing the next frame + ws.frameReader = frame + return ErrFrameTooLarge + } + payloadType := frame.PayloadType() + data, err := ioutil.ReadAll(frame) + if err != nil { + return err + } + return cd.Unmarshal(data, payloadType, v) +} + +func marshal(v interface{}) (msg []byte, payloadType byte, err error) { + switch data := v.(type) { + case string: + return []byte(data), TextFrame, nil + case []byte: + return data, BinaryFrame, nil + } + return nil, UnknownFrame, ErrNotSupported +} + +func unmarshal(msg []byte, payloadType byte, v interface{}) (err error) { + switch data := v.(type) { + case *string: + *data = string(msg) + return nil + case *[]byte: + *data = msg + return nil + } + return ErrNotSupported +} + +/* +Message is a codec to send/receive text/binary data in a frame on WebSocket connection. +To send/receive text frame, use string type. +To send/receive binary frame, use []byte type. + +Trivial usage: + + import "websocket" + + // receive text frame + var message string + websocket.Message.Receive(ws, &message) + + // send text frame + message = "hello" + websocket.Message.Send(ws, message) + + // receive binary frame + var data []byte + websocket.Message.Receive(ws, &data) + + // send binary frame + data = []byte{0, 1, 2} + websocket.Message.Send(ws, data) + +*/ +var Message = Codec{marshal, unmarshal} + +func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err error) { + msg, err = json.Marshal(v) + return msg, TextFrame, err +} + +func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) { + return json.Unmarshal(msg, v) +} + +/* +JSON is a codec to send/receive JSON data in a frame from a WebSocket connection. + +Trivial usage: + + import "websocket" + + type T struct { + Msg string + Count int + } + + // receive JSON type T + var data T + websocket.JSON.Receive(ws, &data) + + // send JSON type T + websocket.JSON.Send(ws, data) +*/ +var JSON = Codec{jsonMarshal, jsonUnmarshal} diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 0000000..5a62447 --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,43 @@ +# github.com/cyrilix/robocar-base v0.0.0-20191227154304-47d48c39b0a2 +github.com/cyrilix/robocar-base/cli +github.com/cyrilix/robocar-base/mqttdevice +github.com/cyrilix/robocar-base/service +github.com/cyrilix/robocar-base/types +# github.com/eclipse/paho.mqtt.golang v1.2.0 +github.com/eclipse/paho.mqtt.golang +github.com/eclipse/paho.mqtt.golang/packets +# golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 +golang.org/x/net/internal/socks +golang.org/x/net/proxy +golang.org/x/net/websocket +# periph.io/x/periph v3.6.2+incompatible +periph.io/x/periph +periph.io/x/periph/conn +periph.io/x/periph/conn/gpio +periph.io/x/periph/conn/gpio/gpioreg +periph.io/x/periph/conn/gpio/gpiostream +periph.io/x/periph/conn/i2c +periph.io/x/periph/conn/i2c/i2creg +periph.io/x/periph/conn/physic +periph.io/x/periph/conn/pin +periph.io/x/periph/conn/pin/pinreg +periph.io/x/periph/conn/spi +periph.io/x/periph/conn/spi/spireg +periph.io/x/periph/experimental/devices/pca9685 +periph.io/x/periph/host +periph.io/x/periph/host/allwinner +periph.io/x/periph/host/am335x +periph.io/x/periph/host/bcm283x +periph.io/x/periph/host/beagle/black +periph.io/x/periph/host/beagle/bone +periph.io/x/periph/host/beagle/green +periph.io/x/periph/host/chip +periph.io/x/periph/host/cpu +periph.io/x/periph/host/distro +periph.io/x/periph/host/fs +periph.io/x/periph/host/odroidc1 +periph.io/x/periph/host/pine64 +periph.io/x/periph/host/pmem +periph.io/x/periph/host/rpi +periph.io/x/periph/host/sysfs +periph.io/x/periph/host/videocore diff --git a/vendor/periph.io/x/periph/.gitignore b/vendor/periph.io/x/periph/.gitignore new file mode 100644 index 0000000..ba077a4 --- /dev/null +++ b/vendor/periph.io/x/periph/.gitignore @@ -0,0 +1 @@ +bin diff --git a/vendor/periph.io/x/periph/.gohci.yml b/vendor/periph.io/x/periph/.gohci.yml new file mode 100644 index 0000000..7eea21c --- /dev/null +++ b/vendor/periph.io/x/periph/.gohci.yml @@ -0,0 +1,254 @@ +# See https://github.com/periph/gohci +version: 1 +workers: +# BeagleBone Green Wireles by SeedStudio. +# https://beagleboard.org/green-wireless +- name: beaglebone-4373 + checks: + - cmd: + - go + - install + - -v + - ./cmd/headers-list + - ./cmd/i2c-list + - ./cmd/periph-info + - ./cmd/periph-smoketest + - ./cmd/spi-list + - cmd: + - periph-info + - cmd: + - headers-list + - -f + - cmd: + - i2c-list + - cmd: + - spi-list + - cmd: + - periph-smoketest + - gpio + - -pin1 + - P8_45 + - -pin2 + - P8_46 +# C.H.I.P. by Next Thing Co. The company closed its doors. +- name: chip-a87d + checks: + - cmd: + - go + - install + - -v + - ./cmd/gpio-list + - ./cmd/headers-list + - ./cmd/i2c-list + - ./cmd/periph-info + - ./cmd/periph-smoketest + - ./cmd/spi-list + - cmd: + - periph-info + - cmd: + - gpio-list + - -f + - cmd: + - headers-list + - -f + - cmd: + - i2c-list + - cmd: + - spi-list + - cmd: + - periph-smoketest + - chip + - cmd: + - periph-smoketest + - i2c-testboard + - -bus + - 1 + - cmd: + - periph-smoketest + - onewire-testboard + - -i2cbus + - 1 + - cmd: + - periph-smoketest + - sysfs-benchmark + - -p + - 1013 + - -short + - cmd: + - periph-smoketest + - sysfs-benchmark + - -p + - 132 + - -short + - cmd: + - periph-smoketest + - allwinner-benchmark + - -p + - 132 + - -short + - cmd: + - periph-smoketest + - spi-testboard +# Old MacBook Pro on 10.9. +- name: mbp + checks: + - cmd: + - go + - test + - -race + - ./... + - cmd: + - go + - install + - -v + - ./cmd/gpio-list + - ./cmd/headers-list + - ./cmd/i2c-list + - ./cmd/periph-info + - ./cmd/spi-list + - cmd: + - periph-info + - cmd: + - gpio-list + - -f + - cmd: + - headers-list + - -f + - cmd: + - i2c-list + - cmd: + - spi-list +# ODROID-C1+ by HardKernel +# https://www.hardkernel.com/shop/odroid-c1/ +- name: odroid-483d + checks: + - cmd: + - go + - test + - -cover + - -bench=. + - -benchtime=1000ms + - -benchmem + - ./... + - ./... + - cmd: + - go + - install + - -v + - ./cmd/gpio-list + - ./cmd/headers-list + - ./cmd/i2c-list + - ./cmd/periph-info + - ./cmd/periph-smoketest + - ./cmd/spi-list + - cmd: + - periph-info + - cmd: + - gpio-list + - -f + - cmd: + - headers-list + - -f + - cmd: + - i2c-list + - cmd: + - spi-list + - cmd: + - periph-smoketest + - odroid-c1 + - cmd: + - periph-smoketest + - i2c-testboard + - cmd: + - periph-smoketest + - onewire-testboard + - cmd: + - periph-smoketest + - spi-testboard + - cmd: + - periph-smoketest + - sysfs-benchmark + - -p + - 97 + - -short +# Raspberry Pi 3 +- name: raspberrypi-2f34 + checks: + - cmd: + - go + - install + - -v + - ./cmd/gpio-list + - ./cmd/headers-list + - ./cmd/i2c-list + - ./cmd/periph-info + - ./cmd/periph-smoketest + - ./cmd/spi-list + - cmd: + - periph-info + - cmd: + - gpio-list + - -f + - cmd: + - headers-list + - -f + - cmd: + - i2c-list + - cmd: + - spi-list + - cmd: + - periph-smoketest + - i2c-testboard + - cmd: + - periph-smoketest + - onewire-testboard + - -i2cbus + - 1 + - cmd: + - periph-smoketest + - spi-testboard + - cmd: + - periph-smoketest + - sysfs-benchmark + - -p + - 12 + - -short + - cmd: + - periph-smoketest + - bcm283x-benchmark + - -p + - 12 + - -short + - cmd: + - periph-smoketest + - gpio + - -pin1 + - P1_15 + - -pin2 + - P1_16 + - cmd: + - periph-smoketest + - bcm283x + - -quick +# Laptop on Windows 10. +- name: win10 + checks: + - cmd: + - go + - test + - -cover + - -bench=. + - -benchtime=1000ms + - -benchmem + - ./... + - cmd: + - go + - test + - -race + - ./... + - cmd: + - go + - vet + - -all + - -unsafeptr=false + - ./... diff --git a/vendor/periph.io/x/periph/.travis.yml b/vendor/periph.io/x/periph/.travis.yml new file mode 100644 index 0000000..87d7fba --- /dev/null +++ b/vendor/periph.io/x/periph/.travis.yml @@ -0,0 +1,53 @@ +# Copyright 2019 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. + +language: go +sudo: false +dist: xenial +go_import_path: periph.io/x/periph + +matrix: + include: + - go: 1.12.x + env: GO111MODULE=on + cache: + directories: + # go1.10+ 'go test' cache on linux (macOS and Windows are # different). + - $HOME/.cache/go-build + # go1.11+ with GO111MODULE=on + - $GOPATH/pkg/mod + # Cache tools sources. Manually verified that both misspell and ineffassign + # only depend on the stdlib. + - $GOPATH/src/github\.com + # For shadow. + - $GOPATH/src/golang\.org + # Dear future me: if you touch this line, don't forget to update the + # conditions below! + - go: 1.7.6 + +before_script: + - echo $TRAVIS_GO_VERSION + - go get -t -v periph.io/x/periph/... + - if [[ $TRAVIS_GO_VERSION != 1.7.6 ]]; then go get -u -v github.com/client9/misspell/cmd/misspell github.com/gordonklaus/ineffassign golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow; fi + +script: + # Checks run on latest version. + - if [[ $TRAVIS_GO_VERSION != 1.7.6 ]]; then echo 'Check Code is well formatted'; ! gofmt -s -d . | read; fi + - if [[ $TRAVIS_GO_VERSION != 1.7.6 ]]; then echo 'Looking for external dependencies:'; go list -f '{{join .Imports "\n"}}' periph.io/x/periph/... | sort | uniq | grep -v ^periph.io/x/periph | xargs go list -f '{{if not .Standard}}- {{.ImportPath}}{{end}}'; fi + - if [[ $TRAVIS_GO_VERSION != 1.7.6 ]]; then echo 'Erroring on external dependencies:'; ! go list -f '{{join .Imports "\n"}}' periph.io/x/periph/... | sort | uniq | grep -v ^periph.io/x/periph | xargs go list -f '{{if not .Standard}}Remove {{.ImportPath}}{{end}}' | grep -q Remove; fi + - if [[ $TRAVIS_GO_VERSION != 1.7.6 ]]; then echo 'Erroring on /host depending on /devices:'; ! go list -f '{{.ImportPath}} depends on {{join .Imports ", "}}' periph.io/x/periph/host/... | sort | uniq | grep periph.io/x/periph/devices; fi + - if [[ $TRAVIS_GO_VERSION != 1.7.6 ]]; then echo 'Erroring on /conn depending on /devices:'; ! go list -f '{{.ImportPath}} depends on {{join .Imports ", "}}' periph.io/x/periph/conn/... | sort | uniq | grep periph.io/x/periph/devices; fi + - if [[ $TRAVIS_GO_VERSION != 1.7.6 ]]; then echo 'Erroring on /conn depending on /host:'; ! go list -f '{{.ImportPath}} depends on {{join .Imports ", "}}' periph.io/x/periph/conn/... | sort | uniq | grep periph.io/x/periph/host; fi + - if [[ $TRAVIS_GO_VERSION != 1.7.6 ]]; then echo 'Erroring on misspelling'; ! misspell . | grep a; fi + - if [[ $TRAVIS_GO_VERSION != 1.7.6 ]]; then ineffassign .; fi + - if [[ $TRAVIS_GO_VERSION != 1.7.6 ]]; then ! go vet -vettool=$(which shadow) ./... |& grep -v '"err"' | grep -e '^[^#]'; fi + - if [[ $TRAVIS_GO_VERSION != 1.7.6 ]]; then bash -c 'set -e; echo "" > coverage.txt; for d in $(go list ./...); do go test -covermode=count -coverprofile=p.out $d; if [ -f p.out ]; then cat p.out >> coverage.txt; rm p.out; fi; done'; fi + - if [[ $TRAVIS_GO_VERSION != 1.7.6 ]]; then go test -race ./...; fi + + # Checks run on older version. + - if [[ $TRAVIS_GO_VERSION == 1.7.6 ]]; then go test ./...; fi + - if [[ $TRAVIS_GO_VERSION == 1.7.6 ]]; then if find . -path ./.git -prune -o -type f -executable -print | grep -e . ; then echo 'Do not commit executables'; false; fi; fi + +after_success: + - if [[ $TRAVIS_GO_VERSION != 1.7.6 ]]; then bash <(curl -s https://codecov.io/bash); fi diff --git a/vendor/periph.io/x/periph/AUTHORS b/vendor/periph.io/x/periph/AUTHORS new file mode 100644 index 0000000..e8ff861 --- /dev/null +++ b/vendor/periph.io/x/periph/AUTHORS @@ -0,0 +1,15 @@ +# This is the list of The Periph Authors for copyright purposes. +# +# This does not necessarily list everyone who has contributed code, since in +# some cases, their employer may be the copyright holder. To see the full list +# of contributors, see the revision history in source control. +Cássio Botaro +Fractal Industries, Inc +Google Inc. +Josh Gardiner +Matt Aimonetti +Max Ekman +Rifiniti, Inc +Stephan Sperber +Thorsten von Eicken + diff --git a/vendor/periph.io/x/periph/CONTRIBUTING.md b/vendor/periph.io/x/periph/CONTRIBUTING.md new file mode 100644 index 0000000..56a90a0 --- /dev/null +++ b/vendor/periph.io/x/periph/CONTRIBUTING.md @@ -0,0 +1,4 @@ +# Contributing + +Thanks for contributing to the project! Please look at [the periph contribution +guidelines](https://periph.io/project/contributing/) first. diff --git a/vendor/periph.io/x/periph/CONTRIBUTORS b/vendor/periph.io/x/periph/CONTRIBUTORS new file mode 100644 index 0000000..2e86a9b --- /dev/null +++ b/vendor/periph.io/x/periph/CONTRIBUTORS @@ -0,0 +1,41 @@ +# This is the official list of people who can contribute +# (and typically have contributed) code to the periph repository. +# The AUTHORS file lists the copyright holders; this file +# lists people. For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. +# +# Names should be added to this file only after verifying that +# the individual or the individual's organization has agreed to +# the appropriate Contributor License Agreement, found here: +# +# https://cla.developers.google.com/ +# +# When adding J Random Contributor's name to this file, +# either J's name or J's organization's name should be +# added to the AUTHORS file, depending on whether the +# individual or corporate CLA was used. + +# Names should be added to this file like so: +# Individual's name +# Individual's name +# +# An entry with multiple email addresses specifies that the +# first address should be used in the submit logs and +# that the other addresses should be recognized as the +# same person when interacting with Gerrit. + +# Please keep the list sorted. + +Cássio Botaro +Eugene Dzhurynsky +Hidetoshi Shimokawa +John Maguire +Josh Gardiner +Marc-Antoine Ruel +Matt Aimonetti +Max Ekman +Matias Insaurralde +Seán C McCord +Stephan Sperber +Thorsten von Eicken + diff --git a/vendor/periph.io/x/periph/LICENSE b/vendor/periph.io/x/periph/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/vendor/periph.io/x/periph/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/periph.io/x/periph/Makefile b/vendor/periph.io/x/periph/Makefile new file mode 100644 index 0000000..53b7fb4 --- /dev/null +++ b/vendor/periph.io/x/periph/Makefile @@ -0,0 +1,59 @@ +# Copyright 2016 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. + +# This Makefile captures common tasks for the periph library. The hope is that this Makefile can remain +# simple and straightforward... + +# *** This Makefile is a work in progress, please help impove it! *** + +# not sure yet what all should do... +all: + @echo Available targets: test build + +.PHONY: all test clean depend + +# test runs the platform independent tests +# (gofmt|grep is used to obtain a non-zero exit status if the formatting is off) +test: + go test ./... + @if gofmt -l . | grep .go; then \ + echo "Repo contains improperly formatted go files; run gofmt on above files" && exit 1; \ + else echo "OK gofmt"; fi + -go vet -unsafeptr=false ./... + +# BUILD +# +# The build target cross compiles each program in cmd to a binary for each platform in the bin +# directory. It is assumed that each command has a main.go file in its directory. Trying to keep all +# this relatively simple and not descend into makefile hell... + +# Get a list of all main.go in cmd subdirs +# MAINS becomes: cmd/gpio-list/main.go cmd/periph-info/main.go ... +MAINS := $(wildcard cmd/*/main.go) +# Get a list of all the commands, i.e. names of dirs that contain a main.go +# CMDS becomes: gpio-list periph-info ... +CMDS := $(patsubst cmd/%/main.go,%,$(MAINS)) +# Get a list of binaries to build +# BINS becomes: bin/gpio-list-arm bin/periph-info-arm ... bin/gpio-list-arm64 bin/periph-info-arm64 ... +ARCHS := arm arm64 amd64 win64.exe +BINS=$(foreach arch,$(ARCHS),$(foreach cmd,$(CMDS),bin/$(cmd)-$(arch))) + +build: depend bin $(BINS) +bin: + mkdir bin + +# Rules to build binaries for a command in cmd. The prereqs could be improved... +bin/%-arm: cmd/%/*.go + GOARCH=arm GOOS=linux go build -o $@ ./cmd/$* +bin/%-arm64: cmd/%/*.go + GOARCH=arm64 GOOS=linux go build -o $@ ./cmd/$* +bin/%-amd64: cmd/%/*.go + GOARCH=amd64 GOOS=linux go build -o $@ ./cmd/$* +bin/%-win64.exe: cmd/%/*.go + GOARCH=amd64 GOOS=windows go build -o $@ ./cmd/$* + +# clean removes all compiled binaries +clean: + rm bin/*-* + rmdir bin diff --git a/vendor/periph.io/x/periph/README.md b/vendor/periph.io/x/periph/README.md new file mode 100644 index 0000000..dcb3ed1 --- /dev/null +++ b/vendor/periph.io/x/periph/README.md @@ -0,0 +1,59 @@ +# periph - Peripherals I/O in Go + +[![mascot](https://raw.githubusercontent.com/periph/website/master/site/static/img/periph-mascot-280.png)](https://periph.io/) + +Documentation is at https://periph.io + +[![GoDoc](https://godoc.org/periph.io/x/periph?status.svg)](https://godoc.org/periph.io/x/periph) +[![Go Report Card](https://goreportcard.com/badge/periph.io/x/periph)](https://goreportcard.com/report/periph.io/x/periph) +[![Coverage Status](https://codecov.io/gh/google/periph/graph/badge.svg)](https://codecov.io/gh/google/periph) +[![Build Status](https://travis-ci.org/google/periph.svg)](https://travis-ci.org/google/periph) + + +Join us for a chat on +[gophers.slack.com/messages/periph](https://gophers.slack.com/messages/periph), +get an [invite here](https://invite.slack.golangbridge.org/). + + +## Example + +Blink a LED: + +~~~go +package main + +import ( + "time" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/host" + "periph.io/x/periph/host/rpi" +) + +func main() { + host.Init() + t := time.NewTicker(500 * time.Millisecond) + for l := gpio.Low; ; l = !l { + rpi.P1_33.Out(l) + <-t.C + } +} +~~~ + +Curious? Look at [supported devices](https://periph.io/device/) for more +examples! + + +## Authors + +`periph` was initiated with ❤️️ and passion by [Marc-Antoine +Ruel](https://github.com/maruel). The full list of contributors is in +[AUTHORS](https://github.com/google/periph/blob/master/AUTHORS) and +[CONTRIBUTORS](https://github.com/google/periph/blob/master/CONTRIBUTORS). + + +## Disclaimer + +This is not an official Google product (experimental or otherwise), it +is just code that happens to be owned by Google. + +This project is not affiliated with the Go project. diff --git a/vendor/periph.io/x/periph/conn/conn.go b/vendor/periph.io/x/periph/conn/conn.go new file mode 100644 index 0000000..68e4f7a --- /dev/null +++ b/vendor/periph.io/x/periph/conn/conn.go @@ -0,0 +1,103 @@ +// Copyright 2016 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 conn + +import "strconv" + +// Resource is a basic resource (like a gpio pin) or a device. +type Resource interface { + // String returns a human readable identifier representing this resource in a + // descriptive way for the user. It is the same signature as fmt.Stringer. + String() string + // Halt stops the resource. + // + // Unlike a Conn, a Resource may not be closable, On the other hand, a + // resource can be halted. What halting entails depends on the resource + // device but it should stop motion, sensing loop, light emission or PWM + // output and go back into an inert state. + Halt() error +} + +// Duplex declares whether communication can happen simultaneously both ways. +// +// Some protocol can be either depending on configuration settings, like UART. +type Duplex int + +const ( + // DuplexUnknown is used when the duplex of a connection is yet to be known. + // + // Some protocol can be configured either as half-duplex or full-duplex and + // the connection is not yet is a determinate state. + DuplexUnknown Duplex = 0 + // Half means that communication can only occurs one way at a time. + // + // Examples include 1-wire and I²C. + Half Duplex = 1 + // Full means that communication occurs simultaneously both ways in a + // synchronized manner. + // + // Examples include SPI (except 3-wire variant). + Full Duplex = 2 +) + +const duplexName = "DuplexUnknownHalfFull" + +var duplexIndex = [...]uint8{0, 13, 17, 21} + +func (i Duplex) String() string { + if i < 0 || i >= Duplex(len(duplexIndex)-1) { + return "Duplex(" + strconv.Itoa(int(i)) + ")" + } + return duplexName[duplexIndex[i]:duplexIndex[i+1]] +} + +// Conn defines the interface for a connection on a point-to-point +// communication channel. +// +// The connection can either be unidirectional (read-only, write-only) or +// bidirectional (read-write). It can either be half-duplex or full duplex. +// +// This is the lowest common denominator for all point-to-point communication +// channels. +// +// Implementation are expected but not required to also implement the following +// interfaces: +// +// - fmt.Stringer which returns something meaningful to the user like "SPI0.1", +// "I2C1.76", "COM6", etc. +// +// - io.Reader and io.Writer as a way to use io.Copy() for half duplex +// operation. +// +// - io.Closer for the owner of the communication channel. +type Conn interface { + String() string + // Tx does a single transaction. + // + // For full duplex protocols (generally SPI, UART), the two buffers must have + // the same length as both reading and writing happen simultaneously. + // + // For half duplex protocols (I²C), there is no restriction as reading + // happens after writing, and r can be nil. + // + // Query Limits.MaxTxSize() to know if there is a limit on the buffer size + // per Tx() call. + Tx(w, r []byte) error + // Duplex returns the current duplex setting for this point-to-point + // connection. + // + // It is expected to be either Half or Full unless the connection itself is + // in an unknown state. + Duplex() Duplex +} + +// Limits returns information about the connection's limits. +type Limits interface { + // MaxTxSize returns the maximum allowed data size to be sent as a single + // I/O. + // + // Returns 0 if undefined. + MaxTxSize() int +} diff --git a/vendor/periph.io/x/periph/conn/doc.go b/vendor/periph.io/x/periph/conn/doc.go new file mode 100644 index 0000000..2af0df5 --- /dev/null +++ b/vendor/periph.io/x/periph/conn/doc.go @@ -0,0 +1,63 @@ +// Copyright 2016 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 conn defines core interfaces for protocols and connections. +// +// This package and its subpackages describe the base interfaces to connect the +// software with the real world. It doesn't contain any implementation but +// includes registries to enable the application to discover the available +// hardware. +// +// Concepts +// +// periph uses 3 layered concepts for interfacing: +// +// Bus → Port → Conn +// +// Not every subpackage expose all 3 concepts. In fact, most packages don't. +// For example, SPI doesn't expose Bus as the OSes generally only expose the +// Port, that is, a Chip Select (CS) line must be selected right upfront to get +// an handle. For I²C, there's no Port to configure, so selecting a "slave" +// address is sufficient to jump directly from a Bus to a Conn. +// +// periph doesn't have yet a concept of star-like communication network, like +// an IP network. +// +// Bus +// +// A Bus is a multi-point communication channel where one "master" and multiple +// "slaves" communicate together. In the case of periph, the Bus handle is +// assumed to be the "master". The "master" generally initiates communications +// and selects the "slave" to talk to. +// +// As the "master" selects a "slave" over a bus, a virtual Port is +// automatically created. +// +// Examples include SPI, I²C and 1-wire. In each case, selecting a +// communication line (Chip Select (CS) line for SPI, address for I²C or +// 1-wire) converts the Bus into a Port. +// +// Port +// +// A port is a point-to-point communication channel that is yet to be +// initialized. It cannot be used for communication until it is connected and +// transformed into a Conn. Configuring a Port converts it into a Conn. Not all +// Port need configuration. +// +// Conn +// +// A Conn is a fully configured half or full duplex communication channel that +// is point-to-point, only between two devices. It is ready to use like any +// readable and/or writable pipe. +// +// Subpackages +// +// Most connection-type specific subpackages include subpackages: +// +// → XXXreg: registry as that is populated by the host drivers and that can be +// leveraged by applications. +// +// → XXXtest: fake implementation that can be leveraged when writing device +// driver unit test. +package conn diff --git a/vendor/periph.io/x/periph/conn/duplex_string.go b/vendor/periph.io/x/periph/conn/duplex_string.go new file mode 100644 index 0000000..9736bef --- /dev/null +++ b/vendor/periph.io/x/periph/conn/duplex_string.go @@ -0,0 +1,3 @@ +// Code generated by "stringer -type Duplex"; DO NOT EDIT + +package conn diff --git a/vendor/periph.io/x/periph/conn/gpio/func.go b/vendor/periph.io/x/periph/conn/gpio/func.go new file mode 100644 index 0000000..c5bb8e5 --- /dev/null +++ b/vendor/periph.io/x/periph/conn/gpio/func.go @@ -0,0 +1,26 @@ +// Copyright 2018 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 gpio + +import "periph.io/x/periph/conn/pin" + +// Well known pin functionality. +const ( + // Inputs + IN pin.Func = "IN" // Input + IN_HIGH pin.Func = "In/High" // Read high + IN_LOW pin.Func = "In/Low" // Read low + + // Outputs + OUT pin.Func = "OUT" // Output, drive + OUT_OC pin.Func = "OUT_OPEN" // Output, open collector/drain + OUT_HIGH pin.Func = "Out/High" // Drive high + OUT_LOW pin.Func = "Out/Low" // Drive low; open collector low + + FLOAT pin.Func = "FLOAT" // Input float or Output open collector high + + CLK pin.Func = "CLK" // Clock is a subset of a PWM, with a 50% duty cycle + PWM pin.Func = "PWM" // Pulse Width Modulation, which is a clock with variable duty cycle +) diff --git a/vendor/periph.io/x/periph/conn/gpio/gpio.go b/vendor/periph.io/x/periph/conn/gpio/gpio.go new file mode 100644 index 0000000..2f6112f --- /dev/null +++ b/vendor/periph.io/x/periph/conn/gpio/gpio.go @@ -0,0 +1,329 @@ +// Copyright 2016 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 gpio defines digital pins. +// +// All GPIO implementations are expected to implement PinIO but the device +// driver may accept a more specific one like PinIn or PinOut. +package gpio + +import ( + "errors" + "strconv" + "strings" + "time" + + "periph.io/x/periph/conn/physic" + "periph.io/x/periph/conn/pin" +) + +// Interfaces + +// Level is the level of the pin: Low or High. +type Level bool + +const ( + // Low represents 0v. + Low Level = false + // High represents Vin, generally 3.3v or 5v. + High Level = true +) + +func (l Level) String() string { + if l == Low { + return "Low" + } + return "High" +} + +// Pull specifies the internal pull-up or pull-down for a pin set as input. +type Pull uint8 + +// Acceptable pull values. +const ( + PullNoChange Pull = 0 // Do not change the previous pull resistor setting or an unknown value + Float Pull = 1 // Let the input float + PullDown Pull = 2 // Apply pull-down + PullUp Pull = 3 // Apply pull-up +) + +const pullName = "PullNoChangeFloatPullDownPullUp" + +var pullIndex = [...]uint8{0, 12, 17, 25, 31} + +func (i Pull) String() string { + if i >= Pull(len(pullIndex)-1) { + return "Pull(" + strconv.Itoa(int(i)) + ")" + } + return pullName[pullIndex[i]:pullIndex[i+1]] +} + +// Edge specifies if an input pin should have edge detection enabled. +// +// Only enable it when needed, since this causes system interrupts. +type Edge int + +// Acceptable edge detection values. +const ( + NoEdge Edge = 0 + RisingEdge Edge = 1 + FallingEdge Edge = 2 + BothEdges Edge = 3 +) + +const edgeName = "NoEdgeRisingEdgeFallingEdgeBothEdges" + +var edgeIndex = [...]uint8{0, 6, 16, 27, 36} + +func (i Edge) String() string { + if i >= Edge(len(edgeIndex)-1) { + return "Edge(" + strconv.Itoa(int(i)) + ")" + } + return edgeName[edgeIndex[i]:edgeIndex[i+1]] +} + +const ( + // DutyMax is a duty cycle of 100%. + DutyMax Duty = 1 << 24 + // DutyHalf is a 50% duty PWM, which boils down to a normal clock. + DutyHalf Duty = DutyMax / 2 +) + +// Duty is the duty cycle for a PWM. +// +// Valid values are between 0 and DutyMax. +type Duty int32 + +func (d Duty) String() string { + // TODO(maruel): Implement one fractional number. + return strconv.Itoa(int((d+50)/(DutyMax/100))) + "%" +} + +// Valid returns true if the Duty cycle value is valid. +func (d Duty) Valid() bool { + return d >= 0 && d <= DutyMax +} + +// ParseDuty parses a string and converts it to a Duty value. +func ParseDuty(s string) (Duty, error) { + percent := strings.HasSuffix(s, "%") + if percent { + s = s[:len(s)-1] + } + i64, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return 0, err + } + i := Duty(i64) + if percent { + // TODO(maruel): Add support for fractional number. + if i < 0 { + return 0, errors.New("duty must be >= 0%") + } + if i > 100 { + return 0, errors.New("duty must be <= 100%") + } + return ((i * DutyMax) + 49) / 100, nil + } + if i < 0 { + return 0, errors.New("duty must be >= 0") + } + if i > DutyMax { + return 0, errors.New("duty must be <= " + strconv.Itoa(int(DutyMax))) + } + return i, nil +} + +// PinIn is an input GPIO pin. +// +// It may optionally support internal pull resistor and edge based triggering. +// +// A button is semantically a PinIn. So if you are looking to read from a +// button, PinIn is the interface you are looking for. +type PinIn interface { + pin.Pin + // In setups a pin as an input. + // + // If WaitForEdge() is planned to be called, make sure to use one of the Edge + // value. Otherwise, use NoEdge to not generated unneeded hardware interrupts. + // + // Calling In() will try to empty the accumulated edges but it cannot be 100% + // reliable due to the OS (linux) and its driver. It is possible that on a + // gpio that is as input, doing a quick Out(), In() may return an edge that + // occurred before the Out() call. + In(pull Pull, edge Edge) error + // Read return the current pin level. + // + // Behavior is undefined if In() wasn't used before. + // + // In some rare case, it is possible that Read() fails silently. This happens + // if another process on the host messes up with the pin after In() was + // called. In this case, call In() again. + Read() Level + // WaitForEdge() waits for the next edge or immediately return if an edge + // occurred since the last call. + // + // Only waits for the kind of edge as specified in a previous In() call. + // Behavior is undefined if In() with a value other than NoEdge wasn't called + // before. + // + // Returns true if an edge was detected during or before this call. Return + // false if the timeout occurred or In() was called while waiting, causing the + // function to exit. + // + // Multiple edges may or may not accumulate between two calls to + // WaitForEdge(). The behavior in this case is undefined and is OS driver + // specific. + // + // It is not required to call Read() to reset the edge detection. + // + // Specify -1 to effectively disable timeout. + WaitForEdge(timeout time.Duration) bool + // Pull returns the internal pull resistor if the pin is set as input pin. + // + // Returns PullNoChange if the value cannot be read. + Pull() Pull + // DefaultPull returns the pull that is initialized on CPU/device reset. This + // is useful to determine if the pin is acceptable for operation with + // certain devices. + DefaultPull() Pull +} + +// PinOut is an output GPIO pin. +// +// A LED, a buzzer, a servo, are semantically a PinOut. So if you are looking +// to control these, PinOut is the interface you are looking for. +type PinOut interface { + pin.Pin + // Out sets a pin as output if it wasn't already and sets the initial value. + // + // After the initial call to ensure that the pin has been set as output, it + // is generally safe to ignore the error returned. + // + // Out() tries to empty the accumulated edges detected if the gpio was + // previously set as input but this is not 100% guaranteed due to the OS. + Out(l Level) error + // PWM sets the PWM output on supported pins, if the pin has hardware PWM + // support. + // + // To use as a general purpose clock, set duty to DutyHalf. Some pins may + // only support DutyHalf and no other value. + // + // Using 0 as frequency will use the optimal value as supported/preferred by + // the pin. + // + // To use as a servo, see https://en.wikipedia.org/wiki/Servo_control as an + // explanation how to calculate duty. + PWM(duty Duty, f physic.Frequency) error +} + +// PinIO is a GPIO pin that supports both input and output. It matches both +// interfaces PinIn and PinOut. +// +// A GPIO pin implementing PinIO may fail at either input or output or both. +type PinIO interface { + pin.Pin + // PinIn + In(pull Pull, edge Edge) error + Read() Level + WaitForEdge(timeout time.Duration) bool + Pull() Pull + DefaultPull() Pull + // PinOut + Out(l Level) error + PWM(duty Duty, f physic.Frequency) error +} + +// INVALID implements PinIO and fails on all access. +var INVALID PinIO + +// RealPin is implemented by aliased pin and allows the retrieval of the real +// pin underlying an alias. +// +// Aliases are created by RegisterAlias. Aliases permits presenting a user +// friendly GPIO pin name while representing the underlying real pin. +// +// The purpose of the RealPin is to be able to cleanly test whether an arbitrary +// gpio.PinIO returned by ByName is an alias for another pin, and resolve it. +type RealPin interface { + Real() PinIO // Real returns the real pin behind an Alias +} + +// + +// errInvalidPin is returned when trying to use INVALID. +var errInvalidPin = errors.New("gpio: invalid pin") + +func init() { + INVALID = invalidPin{} +} + +// invalidPin implements PinIO for compatibility but fails on all access. +type invalidPin struct { +} + +func (invalidPin) String() string { + return "INVALID" +} + +func (invalidPin) Halt() error { + return nil +} + +func (invalidPin) Number() int { + return -1 +} + +func (invalidPin) Name() string { + return "INVALID" +} + +func (invalidPin) Function() string { + return "" +} + +func (invalidPin) Func() pin.Func { + return pin.FuncNone +} + +func (invalidPin) SupportedFuncs() []pin.Func { + return nil +} + +func (invalidPin) SetFunc(f pin.Func) error { + return errInvalidPin +} + +func (invalidPin) In(Pull, Edge) error { + return errInvalidPin +} + +func (invalidPin) Read() Level { + return Low +} + +func (invalidPin) WaitForEdge(timeout time.Duration) bool { + return false +} + +func (invalidPin) Pull() Pull { + return PullNoChange +} + +func (invalidPin) DefaultPull() Pull { + return PullNoChange +} + +func (invalidPin) Out(Level) error { + return errInvalidPin +} + +func (invalidPin) PWM(Duty, physic.Frequency) error { + return errInvalidPin +} + +var _ PinIn = INVALID +var _ PinOut = INVALID +var _ PinIO = INVALID +var _ pin.PinFunc = &invalidPin{} diff --git a/vendor/periph.io/x/periph/conn/gpio/gpioreg/gpioreg.go b/vendor/periph.io/x/periph/conn/gpio/gpioreg/gpioreg.go new file mode 100644 index 0000000..65f2de5 --- /dev/null +++ b/vendor/periph.io/x/periph/conn/gpio/gpioreg/gpioreg.go @@ -0,0 +1,213 @@ +// 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 gpioreg defines a registry for the known digital pins. +package gpioreg + +import ( + "errors" + "strconv" + "sync" + + "periph.io/x/periph/conn/gpio" +) + +// ByName returns a GPIO pin from its name, gpio number or one of its aliases. +// +// For example on a Raspberry Pi, the following values will return the same +// GPIO: the gpio as a number "2", the chipset name "GPIO2", the board pin +// position "P1_3", it's function name "I2C1_SDA". +// +// Returns nil if the gpio pin is not present. +func ByName(name string) gpio.PinIO { + mu.Lock() + defer mu.Unlock() + if p, ok := byName[name]; ok { + return p + } + if dest, ok := byAlias[name]; ok { + if p := getByNameDeep(dest); p != nil { + // Wraps the destination in an alias, so the name makes sense to the user. + // The main drawback is that casting into other gpio interfaces like + // gpio.PinPWM requires going through gpio.RealPin first. + return &pinAlias{p, name} + } + } + return nil +} + +// All returns all the GPIO pins available on this host. +// +// The list is guaranteed to be in order of name using 'natural sorting'. +// +// This list excludes aliases. +// +// This list excludes non-GPIO pins like GROUND, V3_3, etc, since they are not +// GPIO. +func All() []gpio.PinIO { + mu.Lock() + defer mu.Unlock() + out := make([]gpio.PinIO, 0, len(byName)) + for _, p := range byName { + out = insertPinByName(out, p) + } + return out +} + +// Aliases returns all pin aliases. +// +// The list is guaranteed to be in order of aliase name. +func Aliases() []gpio.PinIO { + mu.Lock() + defer mu.Unlock() + out := make([]gpio.PinIO, 0, len(byAlias)) + for name, dest := range byAlias { + // Skip aliases that were not resolved. + if p := getByNameDeep(dest); p != nil { + out = insertPinByName(out, &pinAlias{p, name}) + } + } + return out +} + +// Register registers a GPIO pin. +// +// Registering the same pin number or name twice is an error. +// +// The pin registered cannot implement the interface RealPin. +func Register(p gpio.PinIO) error { + name := p.Name() + if len(name) == 0 { + return errors.New("gpioreg: can't register a pin with no name") + } + if r, ok := p.(gpio.RealPin); ok { + return errors.New("gpioreg: can't register pin " + strconv.Quote(name) + ", it is already an alias to " + strconv.Quote(r.Real().String())) + } + + mu.Lock() + defer mu.Unlock() + if orig, ok := byName[name]; ok { + return errors.New("gpioreg: can't register pin " + strconv.Quote(name) + " twice; already registered as " + strconv.Quote(orig.String())) + } + if dest, ok := byAlias[name]; ok { + return errors.New("gpioreg: can't register pin " + strconv.Quote(name) + "; an alias already exist to: " + strconv.Quote(dest)) + } + byName[name] = p + return nil +} + +// RegisterAlias registers an alias for a GPIO pin. +// +// It is possible to register an alias for a pin that itself has not been +// registered yet. It is valid to register an alias to another alias. It is +// valid to register the same alias multiple times, overriding the previous +// alias. +func RegisterAlias(alias string, dest string) error { + if len(alias) == 0 { + return errors.New("gpioreg: can't register an alias with no name") + } + if len(dest) == 0 { + return errors.New("gpioreg: can't register alias " + strconv.Quote(alias) + " with no dest") + } + + mu.Lock() + defer mu.Unlock() + if _, ok := byName[alias]; ok { + return errors.New("gpioreg: can't register alias " + strconv.Quote(alias) + " for a pin that exists") + } + byAlias[alias] = dest + return nil +} + +// Unregister removes a previously registered GPIO pin or alias from the GPIO +// pin registry. +// +// This can happen when a GPIO pin is exposed via an USB device and the device +// is unplugged, or when a generic OS provided pin is superseded by a CPU +// specific implementation. +func Unregister(name string) error { + mu.Lock() + defer mu.Unlock() + if _, ok := byName[name]; ok { + delete(byName, name) + return nil + } + if _, ok := byAlias[name]; ok { + delete(byAlias, name) + return nil + } + return errors.New("gpioreg: can't unregister unknown pin name " + strconv.Quote(name)) +} + +// + +var ( + mu sync.Mutex + byName = map[string]gpio.PinIO{} + byAlias = map[string]string{} +) + +// pinAlias implements an alias for a PinIO. +// +// pinAlias implements the RealPin interface, which allows querying for the +// real pin under the alias. +type pinAlias struct { + gpio.PinIO + name string +} + +// String returns the alias name along the real pin's Name() in parenthesis, if +// known, else the real pin's number. +func (a *pinAlias) String() string { + return a.name + "(" + a.PinIO.Name() + ")" +} + +// Name returns the pinAlias's name. +func (a *pinAlias) Name() string { + return a.name +} + +// Real returns the real pin behind the alias +func (a *pinAlias) Real() gpio.PinIO { + return a.PinIO +} + +// getByNameDeep recursively resolves the aliases to get the pin. +func getByNameDeep(name string) gpio.PinIO { + if p, ok := byName[name]; ok { + return p + } + if dest, ok := byAlias[name]; ok { + if p := getByNameDeep(dest); p != nil { + // Return the deep pin directly, bypassing the aliases. + return p + } + } + return nil +} + +// insertPinByName inserts pin p into list l while keeping l ordered by name. +func insertPinByName(l []gpio.PinIO, p gpio.PinIO) []gpio.PinIO { + n := p.Name() + i := search(len(l), func(i int) bool { return lessNatural(n, l[i].Name()) }) + l = append(l, nil) + copy(l[i+1:], l[i:]) + l[i] = p + 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 +} diff --git a/vendor/periph.io/x/periph/conn/gpio/gpioreg/natsort.go b/vendor/periph.io/x/periph/conn/gpio/gpioreg/natsort.go new file mode 100644 index 0000000..d8d1a76 --- /dev/null +++ b/vendor/periph.io/x/periph/conn/gpio/gpioreg/natsort.go @@ -0,0 +1,76 @@ +// Copyright 2018 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 gpioreg + +import ( + "strconv" +) + +// lessNatural does a 'natural' comparison on the two strings. +// +// It is extracted from https://github.com/maruel/natural. +func lessNatural(a, b string) bool { + for { + if a == b { + return false + } + if p := commonPrefix(a, b); p != 0 { + a = a[p:] + b = b[p:] + } + if ia := digits(a); ia > 0 { + if ib := digits(b); ib > 0 { + // Both sides have digits. + an, aerr := strconv.ParseUint(a[:ia], 10, 64) + bn, berr := strconv.ParseUint(b[:ib], 10, 64) + if aerr == nil && berr == nil { + if an != bn { + return an < bn + } + // Semantically the same digits, e.g. "00" == "0", "01" == "1". In + // this case, only continue processing if there's trailing data on + // both sides, otherwise do lexical comparison. + if ia != len(a) && ib != len(b) { + a = a[ia:] + b = b[ib:] + continue + } + } + } + } + return a < b + } +} + +// commonPrefix returns the common prefix except for digits. +func commonPrefix(a, b string) int { + m := len(a) + if n := len(b); n < m { + m = n + } + if m == 0 { + return 0 + } + _ = a[m-1] + _ = b[m-1] + for i := 0; i < m; i++ { + ca := a[i] + cb := b[i] + if (ca >= '0' && ca <= '9') || (cb >= '0' && cb <= '9') || ca != cb { + return i + } + } + return m +} + +func digits(s string) int { + for i := 0; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + return i + } + } + return len(s) +} diff --git a/vendor/periph.io/x/periph/conn/gpio/gpiostream/gpiostream.go b/vendor/periph.io/x/periph/conn/gpio/gpiostream/gpiostream.go new file mode 100644 index 0000000..849418a --- /dev/null +++ b/vendor/periph.io/x/periph/conn/gpio/gpiostream/gpiostream.go @@ -0,0 +1,220 @@ +// 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 gpiostream defines digital streams. +// +// Warning +// +// This package is still in flux as development is on-going. +package gpiostream + +import ( + "fmt" + "time" + + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/physic" + "periph.io/x/periph/conn/pin" +) + +// Stream is the interface to define a generic stream. +type Stream interface { + // Frequency is the minimum data rate at which the binary stream is usable. + // + // For example, a bit stream may have a 10kHz data rate. + Frequency() physic.Frequency + // Duration of the binary stream. For infinitely looping streams, it is the + // duration of the non-looping part. + Duration() time.Duration +} + +// BitStream is a stream of bits to be written or read. +type BitStream struct { + // Bits is a densely packed bitstream. + // + // The stream is required to be a multiple of 8 samples. + Bits []byte + // Freq is the rate at each the bit (not byte) stream should be processed. + Freq physic.Frequency + // LSBF when true means than Bits is in LSB-first. When false, the data is + // MSB-first. + // + // With MSBF, the first bit processed is the most significant one (0x80). For + // example, I²C, I2S PCM and SPI use MSB-first at the word level. This + // requires to pack words correctly. + // + // With LSBF, the first bit processed is the least significant one (0x01). + // For example, Ethernet uses LSB-first at the byte level and MSB-first at + // the word level. + LSBF bool +} + +// Frequency implements Stream. +func (b *BitStream) Frequency() physic.Frequency { + return b.Freq +} + +// Duration implements Stream. +func (b *BitStream) Duration() time.Duration { + if b.Freq == 0 { + return 0 + } + return b.Freq.Period() * time.Duration(len(b.Bits)*8) +} + +// GoString implements fmt.GoStringer. +func (b *BitStream) GoString() string { + return fmt.Sprintf("&gpiostream.BitStream{Bits: %x, Freq:%s, LSBF:%t}", b.Bits, b.Freq, b.LSBF) +} + +// EdgeStream is a stream of edges to be written. +// +// This struct is more efficient than BitStream for short repetitive pulses, +// like controlling a servo. A PWM can be created by specifying a slice of +// twice the same resolution and make it looping via a Program. +type EdgeStream struct { + // Edges is the list of Level change. It is assumed that the signal starts + // with gpio.High. Use a duration of 0 for Edges[0] to start with a Low + // instead of the default High. + // + // The value is a multiple of Res. Use a 0 value to 'extend' a continuous + // signal that lasts more than "2^16-1*Res" duration by skipping a pulse. + Edges []uint16 + // Res is the minimum resolution at which the edges should be + // rasterized. + // + // The lower the value, the more memory shall be used when rasterized. + Freq physic.Frequency +} + +// Frequency implements Stream. +func (e *EdgeStream) Frequency() physic.Frequency { + return e.Freq +} + +// Duration implements Stream. +func (e *EdgeStream) Duration() time.Duration { + if e.Freq == 0 { + return 0 + } + t := 0 + for _, edge := range e.Edges { + t += int(edge) + } + return e.Freq.Period() * time.Duration(t) +} + +// Program is a loop of streams. +// +// This is itself a stream, it can be used to reduce memory usage when repeated +// patterns are used. +type Program struct { + Parts []Stream // Each part must be a BitStream, EdgeStream or Program + Loops int // Set to -1 to create an infinite loop +} + +// Frequency implements Stream. +func (p *Program) Frequency() physic.Frequency { + if p.Loops == 0 { + return 0 + } + var buf [16]physic.Frequency + freqs := buf[:0] + for _, part := range p.Parts { + if f := part.Frequency(); f != 0 { + freqs = insertFreq(freqs, f) + } + } + if len(freqs) == 0 { + return 0 + } + f := freqs[0] + for i := 1; i < len(freqs); i++ { + if r := freqs[i]; r*2 < f { + break + } + // Take in account Nyquist rate. https://wikipedia.org/wiki/Nyquist_rate + f *= 2 + } + return f +} + +// Duration implements Stream. +func (p *Program) Duration() time.Duration { + if p.Loops == 0 { + return 0 + } + var d time.Duration + for _, s := range p.Parts { + d += s.Duration() + } + if p.Loops > 1 { + d *= time.Duration(p.Loops) + } + return d +} + +// + +// PinIn allows to read a bit stream from a pin. +// +// Caveat +// +// This interface doesn't enable sampling multiple pins in a +// synchronized way or reading in a continuous uninterrupted way. As such, it +// should be considered experimental. +type PinIn interface { + pin.Pin + // StreamIn reads for the pin at the specified resolution to fill the + // provided buffer. + // + // May only support a subset of the structs implementing Stream. + StreamIn(p gpio.Pull, b Stream) error +} + +// PinOut allows to stream to a pin. +// +// The Stream may be a Program, a BitStream or an EdgeStream. If it is a +// Program that is an infinite loop, a separate goroutine can be used to cancel +// the program. In this case StreamOut() returns without an error. +// +// Caveat +// +// This interface doesn't enable streaming to multiple pins in a +// synchronized way or reading in a continuous uninterrupted way. As such, it +// should be considered experimental. +type PinOut interface { + pin.Pin + StreamOut(s Stream) error +} + +// + +// insertFreq inserts in reverse order, highest frequency first. +func insertFreq(l []physic.Frequency, f physic.Frequency) []physic.Frequency { + i := search(len(l), func(i int) bool { return l[i] < f }) + l = append(l, 0) + copy(l[i+1:], l[i:]) + l[i] = f + 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 +} + +var _ Stream = &BitStream{} +var _ Stream = &EdgeStream{} +var _ Stream = &Program{} diff --git a/vendor/periph.io/x/periph/conn/i2c/func.go b/vendor/periph.io/x/periph/conn/i2c/func.go new file mode 100644 index 0000000..f40308e --- /dev/null +++ b/vendor/periph.io/x/periph/conn/i2c/func.go @@ -0,0 +1,13 @@ +// Copyright 2018 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 i2c + +import "periph.io/x/periph/conn/pin" + +// Well known pin functionality. +const ( + SCL pin.Func = "I2C_SCL" // Clock + SDA pin.Func = "I2C_SDA" // Data +) diff --git a/vendor/periph.io/x/periph/conn/i2c/i2c.go b/vendor/periph.io/x/periph/conn/i2c/i2c.go new file mode 100644 index 0000000..279b25b --- /dev/null +++ b/vendor/periph.io/x/periph/conn/i2c/i2c.go @@ -0,0 +1,135 @@ +// Copyright 2016 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 i2c defines the API to communicate with devices over the I²C +// protocol. +// +// As described in https://periph.io/x/periph/conn#hdr-Concepts, periph.io uses +// the concepts of Bus, Port and Conn. +// +// In the package i2c, 'Port' is not exposed, since once you know the I²C +// device address, there's no unconfigured Port to configure. +// +// Instead, the package includes the adapter 'Dev' to directly convert an I²C +// bus 'i2c.Bus' into a connection 'conn.Conn' by only specifying the device +// I²C address. +// +// See https://en.wikipedia.org/wiki/I%C2%B2C for more information. +package i2c + +import ( + "errors" + "io" + "strconv" + + "periph.io/x/periph/conn" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/physic" +) + +// Bus defines the interface a concrete I²C driver must implement. +// +// This interface is consummed by a device driver for a device sitting on a bus. +// +// This interface doesn't implement conn.Conn since a device address must be +// specified. Use i2cdev.Dev as an adapter to get a conn.Conn compatible +// object. +type Bus interface { + String() string + // Tx does a transaction at the specified device address. + // + // Write is done first, then read. One of 'w' or 'r' can be omitted for a + // unidirectional operation. + Tx(addr uint16, w, r []byte) error + // SetSpeed changes the bus speed, if supported. + // + // On linux due to the way the I²C sysfs driver is exposed in userland, + // calling this function will likely affect *all* I²C buses on the host. + SetSpeed(f physic.Frequency) error +} + +// BusCloser is an I²C bus that can be closed. +// +// This interface is meant to be handled by the application and not the device +// driver. A device driver doesn't "own" a bus, hence it must operate on a Bus, +// not a BusCloser. +type BusCloser interface { + io.Closer + Bus +} + +// Pins defines the pins that an I²C bus interconnect is using on the host. +// +// It is expected that a implementer of Bus also implement Pins but this is not +// a requirement. +type Pins interface { + // SCL returns the CLK (clock) pin. + SCL() gpio.PinIO + // SDA returns the DATA pin. + SDA() gpio.PinIO +} + +// Dev is a device on a I²C bus. +// +// It implements conn.Conn. +// +// It saves from repeatedly specifying the device address. +type Dev struct { + Bus Bus + Addr uint16 +} + +func (d *Dev) String() string { + s := "" + if d.Bus != nil { + s = d.Bus.String() + } + return s + "(" + strconv.Itoa(int(d.Addr)) + ")" +} + +// Tx does a transaction by adding the device's address to each command. +// +// It's a wrapper for Bus.Tx(). +func (d *Dev) Tx(w, r []byte) error { + return d.Bus.Tx(d.Addr, w, r) +} + +// Write writes to the I²C bus without reading, implementing io.Writer. +// +// It's a wrapper for Tx() +func (d *Dev) Write(b []byte) (int, error) { + if err := d.Tx(b, nil); err != nil { + return 0, err + } + return len(b), nil +} + +// Duplex always return conn.Half for I²C. +func (d *Dev) Duplex() conn.Duplex { + return conn.Half +} + +// Addr is an I²C slave address. +type Addr uint16 + +// Set sets the Addr to a value represented by the string s. Values maybe in +// decimal or hexadecimal form. Set implements the flag.Value interface. +func (a *Addr) Set(s string) error { + // Allow for only maximum of 10 bits for i2c addresses. + u, err := strconv.ParseUint(s, 0, 10) + if err != nil { + return errI2CSetError + } + *a = Addr(u) + return nil +} + +// String returns an i2c.Addr as a string formated in hexadecimal. +func (a Addr) String() string { + return "0x" + strconv.FormatInt(int64(a), 16) +} + +var errI2CSetError = errors.New("invalid i2c address") + +var _ conn.Conn = &Dev{} diff --git a/vendor/periph.io/x/periph/conn/i2c/i2creg/i2creg.go b/vendor/periph.io/x/periph/conn/i2c/i2creg/i2creg.go new file mode 100644 index 0000000..69c828b --- /dev/null +++ b/vendor/periph.io/x/periph/conn/i2c/i2creg/i2creg.go @@ -0,0 +1,252 @@ +// 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 i2creg defines I²C bus registry to list buses present on the host. +package i2creg + +import ( + "errors" + "strconv" + "strings" + "sync" + + "periph.io/x/periph/conn/i2c" +) + +// Opener opens an handle to a bus. +// +// It is provided by the actual bus driver. +type Opener func() (i2c.BusCloser, error) + +// Ref references an I²C bus. +// +// It is returned by All() to enumerate all registered buses. +type Ref struct { + // Name of the bus. + // + // 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 bus. + 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. + Number int + // Open is the factory to open an handle to this I²C bus. + Open Opener +} + +// Open opens an I²C bus by its name, an alias or its number and returns an +// handle to it. +// +// Specify the empty string "" to get the first available bus. This is the +// recommended default value unless an application knows the exact bus to use. +// +// Each bus can register multiple aliases, each leading to the same bus handle. +// +// "Bus number" is a generic concept that is highly dependent on the platform +// and OS. On some platform, the first bus may have the number 0, 1 or higher. +// 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. +// +// When the I²C bus 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) (i2c.BusCloser, error) { + var r *Ref + var err error + func() { + mu.Lock() + defer mu.Unlock() + if len(byName) == 0 { + err = errors.New("i2creg: no bus 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("i2creg: can't open unknown bus: " + strconv.Quote(name)) + } + return r.Open() +} + +// All returns a copy of all the registered references to all know I²C buses +// available on this host. +// +// The list is sorted by the bus 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 an I²C bus. +// +// Registering the same bus name twice is an error, e.g. o.Name(). o.Number() +// can be -1 to signify that the bus doesn't have an inherent "bus number". A +// good example is a bus provided over a FT232H device connected on an USB bus. +// In this case, the bus name should be created from the serial number of the +// device for unique identification. +func Register(name string, aliases []string, number int, o Opener) error { + if len(name) == 0 { + return errors.New("i2creg: can't register a bus with no name") + } + if o == nil { + return errors.New("i2creg: can't register bus " + strconv.Quote(name) + " with nil Opener") + } + if number < -1 { + return errors.New("i2creg: can't register bus " + strconv.Quote(name) + " with invalid bus number " + strconv.Itoa(number)) + } + if _, err := strconv.Atoi(name); err == nil { + return errors.New("i2creg: can't register bus " + strconv.Quote(name) + " with name being only a number") + } + if strings.Contains(name, ":") { + return errors.New("i2creg: can't register bus " + strconv.Quote(name) + " with name containing ':'") + } + for _, alias := range aliases { + if len(alias) == 0 { + return errors.New("i2creg: can't register bus " + strconv.Quote(name) + " with an empty alias") + } + if name == alias { + return errors.New("i2creg: can't register bus " + strconv.Quote(name) + " with an alias the same as the bus name") + } + if _, err := strconv.Atoi(alias); err == nil { + return errors.New("i2creg: can't register bus " + strconv.Quote(name) + " with an alias that is a number: " + strconv.Quote(alias)) + } + if strings.Contains(alias, ":") { + return errors.New("i2creg: can't register bus " + strconv.Quote(name) + " with an alias containing ':': " + strconv.Quote(alias)) + } + } + + mu.Lock() + defer mu.Unlock() + if _, ok := byName[name]; ok { + return errors.New("i2creg: can't register bus " + strconv.Quote(name) + " twice") + } + if _, ok := byAlias[name]; ok { + return errors.New("i2creg: can't register bus " + strconv.Quote(name) + " twice; it is already an alias") + } + if number != -1 { + if _, ok := byNumber[number]; ok { + return errors.New("i2creg: can't register bus " + strconv.Quote(name) + "; bus number " + strconv.Itoa(number) + " is already registered") + } + } + for _, alias := range aliases { + if _, ok := byName[alias]; ok { + return errors.New("i2creg: can't register bus " + strconv.Quote(name) + " twice; alias " + strconv.Quote(alias) + " is already a bus") + } + if _, ok := byAlias[alias]; ok { + return errors.New("i2creg: can't register bus " + 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 I²C bus. +// +// This can happen when an I²C bus 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("i2creg: can't unregister unknown bus 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 bus. +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 +} diff --git a/vendor/periph.io/x/periph/conn/physic/doc.go b/vendor/periph.io/x/periph/conn/physic/doc.go new file mode 100644 index 0000000..026e2dd --- /dev/null +++ b/vendor/periph.io/x/periph/conn/physic/doc.go @@ -0,0 +1,21 @@ +// Copyright 2018 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 physic declares types for physical input, outputs and measurement +// units. +// +// This includes temperature, humidity, pressure, tension, current, etc. +// +// SI units +// +// The supported S.I. units is a subset of the official ones. +// T tera 10¹² 1000000000000 +// G giga 10⁹ 1000000000 +// M mega 10⁶ 1000000 +// k kilo 10³ 1000 +// m milli 10⁻³ 0.001 +// µ,u micro 10⁻⁶ 0.000001 +// n nano 10⁻⁹ 0.000000001 +// p pico 10⁻¹² 0.000000000001 +package physic diff --git a/vendor/periph.io/x/periph/conn/physic/physic.go b/vendor/periph.io/x/periph/conn/physic/physic.go new file mode 100644 index 0000000..550855f --- /dev/null +++ b/vendor/periph.io/x/periph/conn/physic/physic.go @@ -0,0 +1,42 @@ +// Copyright 2018 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 physic + +import ( + "time" + + "periph.io/x/periph/conn" +) + +// Env represents measurements from an environmental sensor. +type Env struct { + Temperature Temperature + Pressure Pressure + Humidity RelativeHumidity +} + +// SenseEnv represents an environmental sensor. +type SenseEnv interface { + conn.Resource + + // Sense returns the value read from the sensor. Unsupported metrics are not + // modified. + Sense(env *Env) error + // SenseContinuous initiates a continuous sensing at the specified interval. + // + // It is important to call Halt() once done with the sensing, which will turn + // the device off and will close the channel. + SenseContinuous(interval time.Duration) (<-chan Env, error) + // Precision returns this sensor's precision. + // + // The env values are set to the number of bits that are significant for each + // items that this sensor can measure. + // + // Precision is not accuracy. The sensor may have absolute and relative + // errors in its measurement, that are likely well above the reported + // precision. Accuracy may be improved on some sensor by using oversampling, + // or doing oversampling in software. Refer to its datasheet if available. + Precision(env *Env) +} diff --git a/vendor/periph.io/x/periph/conn/physic/units.go b/vendor/periph.io/x/periph/conn/physic/units.go new file mode 100644 index 0000000..ba416f7 --- /dev/null +++ b/vendor/periph.io/x/periph/conn/physic/units.go @@ -0,0 +1,2254 @@ +// Copyright 2018 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 physic + +import ( + "errors" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +// Angle is the measurement of the difference in orientation between two vectors +// stored as an int64 nano radian. +// +// A negative angle is valid. +// +// The highest representable value is a bit over 9.223GRad or 500,000,000,000°. +type Angle int64 + +// String returns the angle formatted as a string in degree. +func (a Angle) String() string { + // Angle is not a S.I. unit, so it must not be prefixed by S.I. prefixes. + if a == 0 { + return "0°" + } + // Round. + prefix := "" + if a < 0 { + a = -a + prefix = "-" + } + switch { + case a < Degree: + v := ((a * 1000) + Degree/2) / Degree + return prefix + "0." + prefixZeros(3, int(v)) + "°" + case a < 10*Degree: + v := ((a * 1000) + Degree/2) / Degree + i := v / 1000 + v = v - i*1000 + return prefix + strconv.FormatInt(int64(i), 10) + "." + prefixZeros(3, int(v)) + "°" + case a < 100*Degree: + v := ((a * 1000) + Degree/2) / Degree + i := v / 1000 + v = v - i*1000 + return prefix + strconv.FormatInt(int64(i), 10) + "." + prefixZeros(2, int(v)) + "°" + case a < 1000*Degree: + v := ((a * 1000) + Degree/2) / Degree + i := v / 1000 + v = v - i*1000 + return prefix + strconv.FormatInt(int64(i), 10) + "." + prefixZeros(1, int(v)) + "°" + case a > maxAngle-Degree: + u := (uint64(a) + uint64(Degree)/2) / uint64(Degree) + v := int64(u) + return prefix + strconv.FormatInt(int64(v), 10) + "°" + default: + v := (a + Degree/2) / Degree + return prefix + strconv.FormatInt(int64(v), 10) + "°" + } +} + +// Set sets the Angle to the value represented by s. Units are to be provided in +// "rad", "deg" or "°" with an optional SI prefix: "p", "n", "u", "µ", "m", "k", +// "M", "G" or "T". +func (a *Angle) Set(s string) error { + d, n, err := atod(s) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s[n:], "Rad", "rad", "Deg", "deg", "°"); found != "" { + return err + } + return notNumberUnitErr("Rad, Deg or °") + case errOverflowsInt64: + // TODO(maruel): Look for suffix, and reuse it. + return maxValueErr(maxAngle.String()) + case errOverflowsInt64Negative: + // TODO(maruel): Look for suffix, and reuse it. + return minValueErr(minAngle.String()) + } + } + return err + } + + var si prefix + if n != len(s) { + r, rsize := utf8.DecodeRuneInString(s[n:]) + if r <= 1 || rsize == 0 { + return errors.New("unexpected end of string") + } + var siSize int + si, siSize = parseSIPrefix(r) + n += siSize + } + + switch s[n:] { + case "Deg", "deg", "°": + degreePerRadian := decimal{ + base: 17453293, + exp: 0, + neg: false, + } + deg, _ := decimalMul(d, degreePerRadian) + // Impossible for precision loss to exceed 9 since the number of + // significant figures in degrees per radian is only 8. + v, overflow := dtoi(deg, int(si)) + if overflow { + if deg.neg { + return minValueErr(minAngle.String()) + } + return maxValueErr(maxAngle.String()) + } + *a = (Angle)(v) + case "Rad", "rad": + v, overflow := dtoi(d, int(si-nano)) + if overflow { + if d.neg { + return minValueErr("-9.223G" + s[n:]) + } + return maxValueErr("9.223G" + s[n:]) + } + *a = (Angle)(v) + case "": + return noUnitErr("Rad, Deg or °") + default: + if found := hasSuffixes(s[n:], "Rad", "rad", "Deg", "deg", "°"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("Rad, Deg or °") + } + return nil +} + +// Well known Angle constants. +const ( + NanoRadian Angle = 1 + MicroRadian Angle = 1000 * NanoRadian + MilliRadian Angle = 1000 * MicroRadian + Radian Angle = 1000 * MilliRadian + + // Theta is 2π. This is equivalent to 360°. + Theta Angle = 6283185307 * NanoRadian + Pi Angle = 3141592653 * NanoRadian + Degree Angle = 17453293 * NanoRadian + + maxAngle Angle = 9223372036854775807 + minAngle Angle = -9223372036854775807 +) + +// Distance is a measurement of length stored as an int64 nano metre. +// +// This is one of the base unit in the International System of Units. +// +// The highest representable value is 9.2Gm. +type Distance int64 + +// String returns the distance formatted as a string in metre. +func (d Distance) String() string { + return nanoAsString(int64(d)) + "m" +} + +// Set sets the Distance to the value represented by s. Units are to +// be provided in "m", "Mile", "Yard", "in", or "ft" with an optional SI +// prefix: "p", "n", "u", "µ", "m", "k", "M", "G" or "T". +func (d *Distance) Set(s string) error { + dc, n, err := atod(s) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s[n:], "in", "ft", "Yard", "yard", "Mile", "mile", "m"); found != "" { + return err + } + return notNumberUnitErr("m, Mile, in, ft or Yard") + case errOverflowsInt64: + // TODO(maruel): Look for suffix, and reuse it. + return maxValueErr(maxDistance.String()) + case errOverflowsInt64Negative: + // TODO(maruel): Look for suffix, and reuse it. + return minValueErr(minDistance.String()) + } + } + return err + } + si := prefix(unit) + if n != len(s) { + r, rsize := utf8.DecodeRuneInString(s[n:]) + if r <= 1 || rsize == 0 { + return errors.New("unexpected end of string") + } + var siSize int + si, siSize = parseSIPrefix(r) + if si == milli || si == mega { + switch s[n:] { + case "m", "Mile", "mile": + si = unit + } + } + if si != unit { + n += siSize + } + } + v, overflow := dtoi(dc, int(si-nano)) + if overflow { + if dc.neg { + return minValueErr(minDistance.String()) + } + return maxValueErr(maxDistance.String()) + } + switch s[n:] { + case "m": + *d = (Distance)(v) + case "Mile", "mile": + switch { + case v > maxMiles: + return maxValueErr("5731Mile") + case v < minMiles: + return minValueErr("-5731Mile") + case v >= 0: + *d = (Distance)((v*1609344 + 500) / 1000) + default: + *d = (Distance)((v*1609344 - 500) / 1000) + } + case "Yard", "yard": + switch { + case v > maxYards: + return maxValueErr("1 Million Yard") + case v < minYards: + return minValueErr("-1 Million Yard") + case v >= 0: + *d = (Distance)((v*9144 + 5000) / 10000) + default: + *d = (Distance)((v*9144 - 5000) / 10000) + } + case "ft": + switch { + case v > maxFeet: + return maxValueErr("3 Million ft") + case v < minFeet: + return minValueErr("-3 Million ft") + case v >= 0: + *d = (Distance)((v*3048 + 5000) / 10000) + default: + *d = (Distance)((v*3048 - 5000) / 10000) + } + case "in": + switch { + case v > maxInches: + return maxValueErr("36 Million inch") + case v < minInches: + return minValueErr("-36 Million inch") + case v >= 0: + *d = (Distance)((v*254 + 5000) / 10000) + default: + *d = (Distance)((v*254 - 5000) / 10000) + } + case "": + return noUnitErr("m, Mile, in, ft or Yard") + default: + if found := hasSuffixes(s[n:], "in", "ft", "Yard", "Mile", "m"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("m, Mile, in, ft or Yard") + } + return nil +} + +// Well known Distance constants. +const ( + NanoMetre Distance = 1 + MicroMetre Distance = 1000 * NanoMetre + MilliMetre Distance = 1000 * MicroMetre + Metre Distance = 1000 * MilliMetre + KiloMetre Distance = 1000 * Metre + MegaMetre Distance = 1000 * KiloMetre + GigaMetre Distance = 1000 * MegaMetre + + // Conversion between Metre and imperial units. + Thou Distance = 25400 * NanoMetre + Inch Distance = 1000 * Thou + Foot Distance = 12 * Inch + Yard Distance = 3 * Foot + Mile Distance = 1760 * Yard + + maxDistance = 9223372036854775807 * NanoMetre + minDistance = -9223372036854775807 * NanoMetre + maxMiles int64 = (int64(maxDistance) - 500) / int64((Mile)/1000000) // ~Max/1609344 + minMiles int64 = (int64(minDistance) + 500) / int64((Mile)/1000000) // ~Min/1609344 + maxYards int64 = (int64(maxDistance) - 5000) / int64((Yard)/100000) // ~Max/9144 + minYards int64 = (int64(minDistance) + 5000) / int64((Yard)/100000) // ~Min/9144 + maxFeet int64 = (int64(maxDistance) - 5000) / int64((Foot)/100000) // ~Max/3048 + minFeet int64 = (int64(minDistance) + 5000) / int64((Foot)/100000) // ~Min/3048 + maxInches int64 = (int64(maxDistance) - 5000) / int64((Inch)/100000) // ~Max/254 + minInches int64 = (int64(minDistance) + 5000) / int64((Inch)/100000) // ~Min/254 +) + +// ElectricCurrent is a measurement of a flow of electric charge stored as an +// int64 nano Ampere. +// +// This is one of the base unit in the International System of Units. +// +// The highest representable value is 9.2GA. +type ElectricCurrent int64 + +// String returns the current formatted as a string in Ampere. +func (c ElectricCurrent) String() string { + return nanoAsString(int64(c)) + "A" +} + +// Set sets the ElectricCurrent to the value represented by s. Units are to +// be provided in "A" with an optional SI prefix: "p", "n", "u", "µ", "m", "k", +// "M", "G" or "T". +func (c *ElectricCurrent) Set(s string) error { + v, n, err := valueOfUnitString(s, nano) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s, "A", "a"); found != "" { + return err + } + return notNumberUnitErr("A") + case errOverflowsInt64: + return maxValueErr(maxElectricCurrent.String()) + case errOverflowsInt64Negative: + return minValueErr(minElectricCurrent.String()) + } + } + return err + } + + switch s[n:] { + case "A", "a": + *c = (ElectricCurrent)(v) + case "": + return noUnitErr("A") + default: + if found := hasSuffixes(s[n:], "A"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("A") + } + + return nil +} + +// Well known ElectricCurrent constants. +const ( + NanoAmpere ElectricCurrent = 1 + MicroAmpere ElectricCurrent = 1000 * NanoAmpere + MilliAmpere ElectricCurrent = 1000 * MicroAmpere + Ampere ElectricCurrent = 1000 * MilliAmpere + KiloAmpere ElectricCurrent = 1000 * Ampere + MegaAmpere ElectricCurrent = 1000 * KiloAmpere + GigaAmpere ElectricCurrent = 1000 * MegaAmpere + + maxElectricCurrent = 9223372036854775807 * NanoAmpere + minElectricCurrent = -9223372036854775807 * NanoAmpere +) + +// ElectricPotential is a measurement of electric potential stored as an int64 +// nano Volt. +// +// The highest representable value is 9.2GV. +type ElectricPotential int64 + +// String returns the tension formatted as a string in Volt. +func (p ElectricPotential) String() string { + return nanoAsString(int64(p)) + "V" +} + +// Set sets the ElectricPotential to the value represented by s. Units are to +// be provided in "V" with an optional SI prefix: "p", "n", "u", "µ", "m", "k", +// "M", "G" or "T". +func (p *ElectricPotential) Set(s string) error { + v, n, err := valueOfUnitString(s, nano) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s, "V", "v"); found != "" { + return err + } + return notNumberUnitErr("V") + case errOverflowsInt64: + return maxValueErr(maxElectricPotential.String()) + case errOverflowsInt64Negative: + return minValueErr(minElectricPotential.String()) + } + } + return err + } + switch s[n:] { + case "V", "v": + *p = (ElectricPotential)(v) + case "": + return noUnitErr("V") + default: + if found := hasSuffixes(s[n:], "V", "v"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("V") + } + return nil +} + +// Well known ElectricPotential constants. +const ( + // Volt is W/A, kg⋅m²/s³/A. + NanoVolt ElectricPotential = 1 + MicroVolt ElectricPotential = 1000 * NanoVolt + MilliVolt ElectricPotential = 1000 * MicroVolt + Volt ElectricPotential = 1000 * MilliVolt + KiloVolt ElectricPotential = 1000 * Volt + MegaVolt ElectricPotential = 1000 * KiloVolt + GigaVolt ElectricPotential = 1000 * MegaVolt + + maxElectricPotential = 9223372036854775807 * NanoVolt + minElectricPotential = -9223372036854775807 * NanoVolt +) + +// ElectricResistance is a measurement of the difficulty to pass an electric +// current through a conductor stored as an int64 nano Ohm. +// +// The highest representable value is 9.2GΩ. +type ElectricResistance int64 + +// String returns the resistance formatted as a string in Ohm. +func (r ElectricResistance) String() string { + return nanoAsString(int64(r)) + "Ω" +} + +// Set sets the ElectricResistance to the value represented by s. Units are to +// be provided in "Ohm", or "Ω" with an optional SI prefix: "p", "n", "u", "µ", +// "m", "k", "M", "G" or "T". +func (r *ElectricResistance) Set(s string) error { + v, n, err := valueOfUnitString(s, nano) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s, "Ohm", "ohm", "Ω"); found != "" { + return err + } + return notNumberUnitErr("Ohm or Ω") + case errOverflowsInt64: + return maxValueErr(maxElectricResistance.String()) + case errOverflowsInt64Negative: + return minValueErr(minElectricResistance.String()) + } + } + return err + } + + switch s[n:] { + case "Ohm", "ohm", "Ω": + *r = (ElectricResistance)(v) + case "": + return noUnitErr("Ohm or Ω") + default: + if found := hasSuffixes(s[n:], "Ohm", "ohm", "Ω"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("Ohm or Ω") + } + return nil +} + +// Well known ElectricResistance constants. +const ( + // Ohm is V/A, kg⋅m²/s³/A². + NanoOhm ElectricResistance = 1 + MicroOhm ElectricResistance = 1000 * NanoOhm + MilliOhm ElectricResistance = 1000 * MicroOhm + Ohm ElectricResistance = 1000 * MilliOhm + KiloOhm ElectricResistance = 1000 * Ohm + MegaOhm ElectricResistance = 1000 * KiloOhm + GigaOhm ElectricResistance = 1000 * MegaOhm + + maxElectricResistance = 9223372036854775807 * NanoOhm + minElectricResistance = -9223372036854775807 * NanoOhm +) + +// Force is a measurement of interaction that will change the motion of an +// object stored as an int64 nano Newton. +// +// A measurement of Force is a vector and has a direction but this unit only +// represents the magnitude. The orientation needs to be stored as a Quaternion +// independently. +// +// The highest representable value is 9.2TN. +type Force int64 + +// String returns the force formatted as a string in Newton. +func (f Force) String() string { + return nanoAsString(int64(f)) + "N" +} + +// Set sets the Force to the value represented by s. Units are to +// be provided in "N", or "lbf" (Pound force) with an optional SI prefix: "p", +// "n", "u", "µ", "m", "k", "M", "G" or "T". +func (f *Force) Set(s string) error { + d, n, err := atod(s) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s[n:], "N", "lbf"); found != "" { + return err + } + return notNumberUnitErr("N or lbf") + case errOverflowsInt64: + // TODO(maruel): Look for suffix, and reuse it. + return maxValueErr(maxForce.String()) + case errOverflowsInt64Negative: + // TODO(maruel): Look for suffix, and reuse it. + return minValueErr(minForce.String()) + } + } + return err + } + + var si prefix + if n != len(s) { + r, rsize := utf8.DecodeRuneInString(s[n:]) + if r <= 1 || rsize == 0 { + return errors.New("unexpected end of string") + } + var siSize int + si, siSize = parseSIPrefix(r) + + n += siSize + } + + switch s[n:] { + case "lbf": + poundForce := decimal{ + base: 4448221615261, + exp: -3, + neg: false, + } + lbf, loss := decimalMul(d, poundForce) + if loss > 9 { + return errors.New("converting to nano Newtons would overflow, consider using nN for maximum precision") + } + v, overflow := dtoi(lbf, int(si)) + if overflow { + if lbf.neg { + return minValueErr("-2.073496519Glbf") + } + return maxValueErr("2.073496519Glbf") + } + *f = (Force)(v) + case "N": + v, overflow := dtoi(d, int(si-nano)) + if overflow { + if d.neg { + return minValueErr(minForce.String()) + } + return maxValueErr(maxForce.String()) + } + *f = (Force)(v) + case "": + return noUnitErr("N or lbf") + default: + if found := hasSuffixes(s[n:], "N", "lbf"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("N or lbf") + } + return nil +} + +// Well known Force constants. +const ( + // Newton is kg⋅m/s². + NanoNewton Force = 1 + MicroNewton Force = 1000 * NanoNewton + MilliNewton Force = 1000 * MicroNewton + Newton Force = 1000 * MilliNewton + KiloNewton Force = 1000 * Newton + MegaNewton Force = 1000 * KiloNewton + GigaNewton Force = 1000 * MegaNewton + + EarthGravity Force = 9806650 * MicroNewton + + // Conversion between Newton and imperial units. + // Pound is both a unit of mass and weight (force). The suffix Force is added + // to disambiguate the measurement it represents. + PoundForce Force = 4448221615 * NanoNewton + + maxForce Force = (1 << 63) - 1 + minForce Force = -((1 << 63) - 1) +) + +// Frequency is a measurement of cycle per second, stored as an int64 micro +// Hertz. +// +// The highest representable value is 9.2THz. +type Frequency int64 + +// String returns the frequency formatted as a string in Hertz. +func (f Frequency) String() string { + return microAsString(int64(f)) + "Hz" +} + +// Set sets the Frequency to the value represented by s. Units are to +// be provided in "Hz" or "rpm" with an optional SI prefix: "p", "n", "u", "µ", +// "m", "k", "M", "G" or "T". +// +// Unlike most Set() functions, "Hz" is assumed by default. +func (f *Frequency) Set(s string) error { + v, n, err := valueOfUnitString(s, micro) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s, "Hz", "hz"); found != "" { + return err + } + return notNumberUnitErr("Hz") + case errOverflowsInt64: + return maxValueErr(maxFrequency.String()) + case errOverflowsInt64Negative: + return minValueErr(minFrequency.String()) + } + } + return err + } + + switch s[n:] { + case "Hz", "hz", "": + *f = (Frequency)(v) + default: + if found := hasSuffixes(s[n:], "Hz", "hz"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("Hz") + } + return nil +} + +// Period returns the duration of one cycle at this frequency. +// +// Frequency above GigaHertz cannot be represented as Duration. +// +// A 0Hz frequency returns a 0s period. +func (f Frequency) Period() time.Duration { + if f == 0 { + return 0 + } + if f < 0 { + return (time.Second*time.Duration(Hertz) - time.Duration(f/2)) / time.Duration(f) + } + return (time.Second*time.Duration(Hertz) + time.Duration(f/2)) / time.Duration(f) +} + +// Duration returns the duration of one cycle at this frequency. +// +// Deprecated: This method is removed in v4.0.0. Use Period() instead. +func (f Frequency) Duration() time.Duration { + return f.Period() +} + +// PeriodToFrequency returns the frequency for a period of this interval. +// +// A 0s period returns a 0Hz frequency. +func PeriodToFrequency(p time.Duration) Frequency { + if p == 0 { + return 0 + } + if p < 0 { + return (Frequency(time.Second)*Hertz - Frequency(p/2)) / Frequency(p) + } + return (Frequency(time.Second)*Hertz + Frequency(p/2)) / Frequency(p) +} + +// Well known Frequency constants. +const ( + // Hertz is 1/s. + MicroHertz Frequency = 1 + MilliHertz Frequency = 1000 * MicroHertz + Hertz Frequency = 1000 * MilliHertz + KiloHertz Frequency = 1000 * Hertz + MegaHertz Frequency = 1000 * KiloHertz + GigaHertz Frequency = 1000 * MegaHertz + TeraHertz Frequency = 1000 * GigaHertz + + // RPM is revolutions per minute. It is used to quantify angular frequency. + RPM Frequency = 16667 * MicroHertz + + maxFrequency = 9223372036854775807 * MicroHertz + minFrequency = -9223372036854775807 * MicroHertz +) + +// Mass is a measurement of mass stored as an int64 nano gram. +// +// This is one of the base unit in the International System of Units. +// +// The highest representable value is 9.2Gg. +type Mass int64 + +// String returns the mass formatted as a string in gram. +func (m Mass) String() string { + return nanoAsString(int64(m)) + "g" +} + +// Set sets the Mass to the value represented by s. Units are to be provided in +// "g", "lb" or "oz" with an optional SI prefix: "p", "n", "u", "µ", "m", "k", +// "M", "G" or "T". +func (m *Mass) Set(s string) error { + d, n, err := atod(s) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s[n:], "g", "lb", "oz"); found != "" { + return err + } + return notNumberUnitErr("g, lb or oz") + case errOverflowsInt64: + // TODO(maruel): Look for suffix, and reuse it. + return maxValueErr(maxMass.String()) + case errOverflowsInt64Negative: + // TODO(maruel): Look for suffix, and reuse it. + return minValueErr(minMass.String()) + } + } + return err + } + + var si prefix + if n != len(s) { + r, rsize := utf8.DecodeRuneInString(s[n:]) + if r <= 1 || rsize == 0 { + return errors.New("unexpected end of string") + } + var siSize int + si, siSize = parseSIPrefix(r) + n += siSize + } + + switch s[n:] { + case "g": + v, overflow := dtoi(d, int(si-nano)) + if overflow { + if d.neg { + return minValueErr(minMass.String()) + } + return maxValueErr(maxMass.String()) + } + *m = (Mass)(v) + case "lb": + gramsPerlb := decimal{ + base: uint64(PoundMass), + exp: 0, + neg: false, + } + lbs, _ := decimalMul(d, gramsPerlb) + v, overflow := dtoi(lbs, int(si)) + if overflow { + if lbs.neg { + return minValueErr(strconv.FormatInt(int64(minPoundMass), 10) + "lb") + } + return maxValueErr(strconv.FormatInt(int64(maxPoundMass), 10) + "lb") + } + *m = (Mass)(v) + case "oz": + gramsPerOz := decimal{ + base: uint64(OunceMass), + exp: 0, + neg: false, + } + oz, _ := decimalMul(d, gramsPerOz) + v, overflow := dtoi(oz, int(si)) + if overflow { + if oz.neg { + return minValueErr(strconv.FormatInt(int64(minOunceMass), 10) + "oz") + } + return maxValueErr(strconv.FormatInt(int64(maxOunceMass), 10) + "oz") + } + *m = (Mass)(v) + case "": + return noUnitErr("g, lb or oz") + default: + if found := hasSuffixes(s[n:], "g", "lb", "oz"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("g, lb or oz") + } + return nil +} + +// Well known Mass constants. +const ( + NanoGram Mass = 1 + MicroGram Mass = 1000 * NanoGram + MilliGram Mass = 1000 * MicroGram + Gram Mass = 1000 * MilliGram + KiloGram Mass = 1000 * Gram + MegaGram Mass = 1000 * KiloGram + GigaGram Mass = 1000 * MegaGram + Tonne Mass = MegaGram + + // Conversion between Gram and imperial units. + // Ounce is both a unit of mass, weight (force) or volume depending on + // context. The suffix Mass is added to disambiguate the measurement it + // represents. + OunceMass Mass = 28349523125 * NanoGram + // Pound is both a unit of mass and weight (force). The suffix Mass is added + // to disambiguate the measurement it represents. + PoundMass Mass = 16 * OunceMass + + Slug Mass = 14593903 * MilliGram + + maxMass Mass = (1 << 63) - 1 + minMass Mass = -((1 << 63) - 1) + + // min and max Pound mass are in lb. + minPoundMass Mass = -20334054 + maxPoundMass Mass = 20334054 + // min and max Ounce mass are in oz. + minOunceMass Mass = -325344874 + maxOunceMass Mass = 325344874 +) + +// Pressure is a measurement of force applied to a surface per unit +// area (stress) stored as an int64 nano Pascal. +// +// The highest representable value is 9.2GPa. +type Pressure int64 + +// String returns the pressure formatted as a string in Pascal. +func (p Pressure) String() string { + return nanoAsString(int64(p)) + "Pa" +} + +// Set sets the Pressure to the value represented by s. Units are to +// be provided in "Pa" with an optional SI prefix: "p", "n", "u", "µ", "m", "k", +// "M", "G" or "T". +func (p *Pressure) Set(s string) error { + v, n, err := valueOfUnitString(s, nano) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s, "Pa"); found != "" { + return err + } + return notNumberUnitErr("Pa") + case errOverflowsInt64: + return maxValueErr(maxPressure.String()) + case errOverflowsInt64Negative: + return minValueErr(minPressure.String()) + } + } + return err + } + + switch s[n:] { + case "Pa": + *p = (Pressure)(v) + case "": + return noUnitErr("Pa") + default: + if found := hasSuffixes(s[n:], "Pa"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("Pa") + } + + return nil +} + +// Well known Pressure constants. +const ( + // Pascal is N/m², kg/m/s². + NanoPascal Pressure = 1 + MicroPascal Pressure = 1000 * NanoPascal + MilliPascal Pressure = 1000 * MicroPascal + Pascal Pressure = 1000 * MilliPascal + KiloPascal Pressure = 1000 * Pascal + MegaPascal Pressure = 1000 * KiloPascal + GigaPascal Pressure = 1000 * MegaPascal + + maxPressure = 9223372036854775807 * NanoPascal + minPressure = -9223372036854775807 * NanoPascal +) + +// RelativeHumidity is a humidity level measurement stored as an int32 fixed +// point integer at a precision of 0.00001%rH. +// +// Valid values are between 0% and 100%. +type RelativeHumidity int32 + +// String returns the humidity formatted as a string. +func (r RelativeHumidity) String() string { + r /= MilliRH + frac := int(r % 10) + if frac == 0 { + return strconv.Itoa(int(r)/10) + "%rH" + } + if frac < 0 { + frac = -frac + } + return strconv.Itoa(int(r)/10) + "." + strconv.Itoa(frac) + "%rH" +} + +// Set sets the RelativeHumidity to the value represented by s. Units are to +// be provided in "%rH" or "%" with an optional SI prefix: "p", "n", "u", "µ", +// "m", "k", "M", "G" or "T". +func (r *RelativeHumidity) Set(s string) error { + // PercentRH is micro + deca. + v, n, err := valueOfUnitString(s, micro+deca) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s[n:], "%rH", "%"); found != "" { + return err + } + return notNumberUnitErr("%rH or %") + case errOverflowsInt64: + return maxValueErr(maxRelativeHumidity.String()) + case errOverflowsInt64Negative: + return minValueErr(minRelativeHumidity.String()) + } + } + return err + } + + switch s[n:] { + case "%rH", "%": + // We need an extra check here to make sure that v will fit inside a + // int32. + switch { + case v > int64(maxRelativeHumidity): + return maxValueErr(maxRelativeHumidity.String()) + case v < int64(minRelativeHumidity): + return minValueErr(minRelativeHumidity.String()) + } + *r = (RelativeHumidity)(v) + case "": + return noUnitErr("%rH or %") + default: + if found := hasSuffixes(s[n:], "%rH", "%"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("%rH or %") + } + + return nil +} + +// Well known RelativeHumidity constants. +const ( + TenthMicroRH RelativeHumidity = 1 // 0.00001%rH + MicroRH RelativeHumidity = 10 * TenthMicroRH // 0.0001%rH + MilliRH RelativeHumidity = 1000 * MicroRH // 0.1%rH + PercentRH RelativeHumidity = 10 * MilliRH // 1%rH + + maxRelativeHumidity RelativeHumidity = 100 * PercentRH + minRelativeHumidity RelativeHumidity = 0 +) + +// Speed is a measurement of magnitude of velocity stored as an int64 nano +// Metre per Second. +// +// The highest representable value is 9.2Gm/s. +type Speed int64 + +// String returns the speed formatted as a string in m/s. +func (sp Speed) String() string { + return nanoAsString(int64(sp)) + "m/s" +} + +// Set sets the Speed to the value represented by s. Units are to be provided in +// "mps"(meters per second), "m/s", "kph", "fps", or "mph" with an optional SI +// prefix: "p", "n", "u", "µ", "m", "k", "M", "G" or "T". +func (sp *Speed) Set(s string) error { + d, n, err := atod(s) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s[n:], "m/s", "mps", "kph", "fps", "mph"); found != "" { + return err + } + return notNumberUnitErr("m/s, mps, kph, fps or mph") + case errOverflowsInt64: + // TODO(maruel): Look for suffix, and reuse it. + return maxValueErr(maxSpeed.String()) + case errOverflowsInt64Negative: + // TODO(maruel): Look for suffix, and reuse it. + return minValueErr(minSpeed.String()) + } + } + return err + } + + var si prefix + if n != len(s) { + r, rsize := utf8.DecodeRuneInString(s[n:]) + if r <= 1 || rsize == 0 { + return errors.New("unexpected end of string") + } + var siSize int + si, siSize = parseSIPrefix(r) + if si == milli { + switch s[n:] { + case "m/s", "mps", "mph": + si = unit + siSize = 0 + } + } + if si == kilo { + switch s[n:] { + case "kph": + si = unit + siSize = 0 + } + } + n += siSize + } + switch s[n:] { + case "m/s", "mps": + v, overflow := dtoi(d, int(si-nano)) + if overflow { + if d.neg { + return minValueErr(minSpeed.String()) + } + return maxValueErr(maxSpeed.String()) + } + *sp = (Speed)(v) + case "kph": + mpsPerkph := decimal{ + base: uint64(KilometrePerHour), + exp: 0, + neg: false, + } + kph, _ := decimalMul(d, mpsPerkph) + v, overflow := dtoi(kph, int(si)) + if overflow { + if kph.neg { + return minValueErr(strconv.FormatInt(int64(minKilometrePerHour), 10) + "kph") + } + return maxValueErr(strconv.FormatInt(int64(maxKilometrePerHour), 10) + "kph") + } + *sp = (Speed)(v) + case "fps": + mpsPerfps := decimal{ + base: uint64(FootPerSecond / 1000), + exp: 3, + neg: false, + } + oz, _ := decimalMul(d, mpsPerfps) + v, overflow := dtoi(oz, int(si)) + if overflow { + if oz.neg { + return minValueErr(strconv.FormatInt(int64(minFootPerSecond), 10) + "fps") + } + return maxValueErr(strconv.FormatInt(int64(maxFootPerSecond), 10) + "fps") + } + *sp = (Speed)(v) + case "mph": + mpsPermph := decimal{ + base: uint64(MilePerHour / 1000), + exp: 3, + neg: false, + } + oz, _ := decimalMul(d, mpsPermph) + v, overflow := dtoi(oz, int(si)) + if overflow { + if oz.neg { + return minValueErr(strconv.FormatInt(int64(minMilePerHour), 10) + "mph") + } + return maxValueErr(strconv.FormatInt(int64(maxMilePerHour), 10) + "mph") + } + *sp = (Speed)(v) + case "": + return noUnitErr("m/s, mps, kph, fps or mph") + default: + if found := hasSuffixes(s[n:], "m/s", "mps", "kph", "fps", "mph"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("m/s, mps, kph, fps or mph") + } + return nil +} + +// Well known Speed constants. +const ( + // MetrePerSecond is m/s. + NanoMetrePerSecond Speed = 1 + MicroMetrePerSecond Speed = 1000 * NanoMetrePerSecond + MilliMetrePerSecond Speed = 1000 * MicroMetrePerSecond + MetrePerSecond Speed = 1000 * MilliMetrePerSecond + KiloMetrePerSecond Speed = 1000 * MetrePerSecond + MegaMetrePerSecond Speed = 1000 * KiloMetrePerSecond + GigaMetrePerSecond Speed = 1000 * MegaMetrePerSecond + + LightSpeed Speed = 299792458 * MetrePerSecond + + KilometrePerHour Speed = 277777778 * NanoMetrePerSecond + MilePerHour Speed = 447040 * MicroMetrePerSecond + FootPerSecond Speed = 304800 * MicroMetrePerSecond + + maxSpeed Speed = (1 << 63) - 1 + minSpeed Speed = -((1 << 63) - 1) + + // Min Max KilometrePerHour are in kph. + minKilometrePerHour Speed = -33204139306 + maxKilometrePerHour Speed = 33204139306 + // Min Max MilePerHour are in mph. + minMilePerHour Speed = -20632095644 + maxMilePerHour Speed = 20632095644 + // Min Max FootPerSecond are in fps. + minFootPerSecond Speed = -30260406945 + maxFootPerSecond Speed = 30260406945 +) + +// Temperature is a measurement of hotness stored as a nano kelvin. +// +// Negative values are invalid. +// +// The highest representable value is 9.2GK. +type Temperature int64 + +// String returns the temperature formatted as a string in °Celsius. +func (t Temperature) String() string { + if t < -ZeroCelsius || t > maxCelsius { + return nanoAsString(int64(t)) + "K" + } + return nanoAsString(int64(t-ZeroCelsius)) + "°C" +} + +// Set sets the Temperature to the value represented by s. Units are to be +// provided in "C", "°C", "F", "°F" or "K" with an optional SI prefix: "p", "n", +// "u", "µ", "m", "k", "M", "G" or "T". +func (t *Temperature) Set(s string) error { + d, n, err := atod(s) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s[n:], "°C", "C", "°F", "F", "K"); found != "" { + return err + } + return notNumberUnitErr("K, °C, C, °F or F") + case errOverflowsInt64: + // TODO(maruel): Look for suffix, and reuse it. + return maxValueErr(maxTemperature.String()) + case errOverflowsInt64Negative: + // TODO(maruel): Look for suffix, and reuse it. + return minValueErr(minTemperature.String()) + } + } + return err + } + + var si prefix + if n != len(s) { + r, rsize := utf8.DecodeRuneInString(s[n:]) + if r <= 1 || rsize == 0 { + return errors.New("unexpected end of string") + } + var siSize int + si, siSize = parseSIPrefix(r) + n += siSize + } + switch s[n:] { + case "F", "°F": + // F to nK nK = 555555555.556*F + 255372222222 + fPerK := decimal{ + base: 555555555556, + exp: -3, + neg: false, + } + f, _ := decimalMul(d, fPerK) + v, overflow := dtoi(f, int(si)) + if overflow { + if f.neg { + return minValueErr("-459.67F") + } + return maxValueErr(strconv.FormatInt(int64(maxFahrenheit), 10) + "F") + } + // We need an extra check here to make sure that will not overflow with + // the addition of ZeroFahrenheit. + switch { + case v > int64(maxTemperature-ZeroFahrenheit): + return maxValueErr(strconv.FormatInt(int64(maxFahrenheit), 10) + "F") + case v < int64(-ZeroFahrenheit): + return minValueErr("-459.67F") + } + v += int64(ZeroFahrenheit) + *t = (Temperature)(v) + case "K": + v, overflow := dtoi(d, int(si-nano)) + if overflow { + if d.neg { + return minValueErr("0K") + } + return maxValueErr(strconv.FormatInt(int64(maxTemperature/1000000000), 10) + "K") + } + if v < 0 { + return minValueErr("0K") + } + *t = (Temperature)(v) + case "C", "°C": + v, overflow := dtoi(d, int(si-nano)) + if overflow { + if d.neg { + return minValueErr("-273.15°C") + } + return maxValueErr(strconv.FormatInt(int64(maxCelsius/1000000000), 10) + "°C") + } + // We need an extra check here to make sure that will not overflow with + // the addition of ZeroCelsius. + switch { + case v > int64(maxCelsius): + return maxValueErr(strconv.FormatInt(int64(maxCelsius/1000000000), 10) + "°C") + case v < int64(-ZeroCelsius): + return minValueErr("-273.15°C") + } + v += int64(ZeroCelsius) + *t = (Temperature)(v) + case "": + return noUnitErr("K, °C, C, °F or F") + default: + if found := hasSuffixes(s[n:], "°C", "C", "°F", "F", "K"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("K, °C, C, °F or F") + } + return nil +} + +// Celsius returns the temperature as a floating number of °Celsius. +func (t Temperature) Celsius() float64 { + return float64(t-ZeroCelsius) / float64(Celsius) +} + +// Fahrenheit returns the temperature as a floating number of °Fahrenheit. +func (t Temperature) Fahrenheit() float64 { + return float64(t-ZeroFahrenheit) / float64(Fahrenheit) +} + +// Well known Temperature constants. +const ( + NanoKelvin Temperature = 1 + MicroKelvin Temperature = 1000 * NanoKelvin + MilliKelvin Temperature = 1000 * MicroKelvin + Kelvin Temperature = 1000 * MilliKelvin + KiloKelvin Temperature = 1000 * Kelvin + MegaKelvin Temperature = 1000 * KiloKelvin + GigaKelvin Temperature = 1000 * MegaKelvin + + // Conversion between Kelvin and Celsius. + ZeroCelsius Temperature = 273150 * MilliKelvin + MilliCelsius Temperature = MilliKelvin + Celsius Temperature = Kelvin + + // Conversion between Kelvin and Fahrenheit. + ZeroFahrenheit Temperature = 255372222222 * NanoKelvin + MilliFahrenheit Temperature = 555555 * NanoKelvin + Fahrenheit Temperature = 555555555 * NanoKelvin + + maxTemperature Temperature = (1 << 63) - 1 + minTemperature Temperature = 0 + + // Maximum Celsius is 9223371763704775807°nC. + maxCelsius Temperature = maxTemperature - ZeroCelsius + + // Maximum Fahrenheit is 16602069204F + maxFahrenheit Temperature = 16602069204 +) + +// Power is a measurement of power stored as a nano watts. +// +// The highest representable value is 9.2GW. +type Power int64 + +// String returns the power formatted as a string in watts. +func (p Power) String() string { + return nanoAsString(int64(p)) + "W" +} + +// Set sets the Power to the value represented by s. Units are to +// be provided in "W" with an optional SI prefix: "p", "n", "u", "µ", "m", "k", +// "M", "G" or "T". +func (p *Power) Set(s string) error { + v, n, err := valueOfUnitString(s, nano) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s, "W", "w"); found != "" { + return err + } + return notNumberUnitErr("W") + case errOverflowsInt64: + return maxValueErr(maxPower.String()) + case errOverflowsInt64Negative: + return minValueErr(minPower.String()) + } + } + return err + } + + switch s[n:] { + case "W", "w": + *p = (Power)(v) + case "": + return noUnitErr("W") + default: + if found := hasSuffixes(s[n:], "W", "w"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("W") + } + + return nil +} + +// Well known Power constants. +const ( + // Watt is unit of power J/s, kg⋅m²⋅s⁻³ + NanoWatt Power = 1 + MicroWatt Power = 1000 * NanoWatt + MilliWatt Power = 1000 * MicroWatt + Watt Power = 1000 * MilliWatt + KiloWatt Power = 1000 * Watt + MegaWatt Power = 1000 * KiloWatt + GigaWatt Power = 1000 * MegaWatt + + maxPower = 9223372036854775807 * NanoWatt + minPower = -9223372036854775807 * NanoWatt +) + +// Energy is a measurement of work stored as a nano joules. +// +// The highest representable value is 9.2GJ. +type Energy int64 + +// String returns the energy formatted as a string in Joules. +func (e Energy) String() string { + return nanoAsString(int64(e)) + "J" +} + +// Set sets the Energy to the value represented by s. Units are to +// be provided in "J" with an optional SI prefix: "p", "n", "u", "µ", "m", "k", +// "M", "G" or "T". +func (e *Energy) Set(s string) error { + v, n, err := valueOfUnitString(s, nano) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s, "J", "j"); found != "" { + return err + } + return notNumberUnitErr("J") + case errOverflowsInt64: + return maxValueErr(maxEnergy.String()) + case errOverflowsInt64Negative: + return minValueErr(minEnergy.String()) + } + } + return err + } + + switch s[n:] { + case "J", "j": + *e = (Energy)(v) + case "": + return noUnitErr("J") + default: + if found := hasSuffixes(s[n:], "J", "j"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("J") + } + + return nil +} + +// Well known Energy constants. +const ( + // Joule is a unit of work. kg⋅m²⋅s⁻² + NanoJoule Energy = 1 + MicroJoule Energy = 1000 * NanoJoule + MilliJoule Energy = 1000 * MicroJoule + Joule Energy = 1000 * MilliJoule + KiloJoule Energy = 1000 * Joule + MegaJoule Energy = 1000 * KiloJoule + GigaJoule Energy = 1000 * MegaJoule + + // BTU (British thermal unit) is the heat required to raise the temperature + // of one pound of water by one degree Fahrenheit. This is the ISO value. + BTU Energy = 1055060 * MilliJoule + + WattSecond Energy = Joule + WattHour Energy = 3600 * Joule + KiloWattHour Energy = 3600 * KiloJoule + + maxEnergy = 9223372036854775807 * NanoJoule + minEnergy = -9223372036854775807 * NanoJoule +) + +// ElectricalCapacitance is a measurement of capacitance stored as a pico farad. +// +// The highest representable value is 9.2MF. +type ElectricalCapacitance int64 + +// String returns the energy formatted as a string in Farad. +func (c ElectricalCapacitance) String() string { + return picoAsString(int64(c)) + "F" +} + +// Set sets the ElectricalCapacitance to the value represented by s. Units are +// to be provided in "F" with an optional SI prefix: "p", "n", "u", "µ", "m", +// "k", "M", "G" or "T". +func (c *ElectricalCapacitance) Set(s string) error { + v, n, err := valueOfUnitString(s, pico) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s, "F", "f"); found != "" { + return err + } + return notNumberUnitErr("F") + case errOverflowsInt64: + return maxValueErr(maxElectricalCapacitance.String()) + case errOverflowsInt64Negative: + return minValueErr(minElectricalCapacitance.String()) + } + } + return err + } + + switch s[n:] { + case "F", "f": + *c = (ElectricalCapacitance)(v) + case "": + return noUnitErr("F") + default: + if found := hasSuffixes(s[n:], "F", "f"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("F") + } + + return nil +} + +// Well known ElectricalCapacitance constants. +const ( + // Farad is a unit of capacitance. kg⁻¹⋅m⁻²⋅s⁴A² + PicoFarad ElectricalCapacitance = 1 + NanoFarad ElectricalCapacitance = 1000 * PicoFarad + MicroFarad ElectricalCapacitance = 1000 * NanoFarad + MilliFarad ElectricalCapacitance = 1000 * MicroFarad + Farad ElectricalCapacitance = 1000 * MilliFarad + KiloFarad ElectricalCapacitance = 1000 * Farad + MegaFarad ElectricalCapacitance = 1000 * KiloFarad + + maxElectricalCapacitance = 9223372036854775807 * PicoFarad + minElectricalCapacitance = -9223372036854775807 * PicoFarad +) + +// LuminousIntensity is a measurement of the quantity of visible light energy +// emitted per unit solid angle with wavelength power weighted by a luminosity +// function which represents the human eye's response to different wavelengths. +// The CIE 1931 luminosity function is the SI standard for candela. +// +// LuminousIntensity is stored as nano candela. +// +// This is one of the base unit in the International System of Units. +// +// The highest representable value is 9.2Gcd. +type LuminousIntensity int64 + +// String returns the energy formatted as a string in Candela. +func (i LuminousIntensity) String() string { + return nanoAsString(int64(i)) + "cd" +} + +// Set sets the LuminousIntensity to the value represented by s. Units are to +// be provided in "cd" with an optional SI prefix: "p", "n", "u", "µ", "m", "k", +// "M", "G" or "T". +func (i *LuminousIntensity) Set(s string) error { + v, n, err := valueOfUnitString(s, nano) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s, "cd"); found != "" { + return err + } + return notNumberUnitErr("cd") + case errOverflowsInt64: + return maxValueErr(maxLuminousIntensity.String()) + case errOverflowsInt64Negative: + return minValueErr(minLuminousIntensity.String()) + } + } + return err + } + + switch s[n:] { + case "cd": + *i = (LuminousIntensity)(v) + case "": + return noUnitErr("cd") + default: + if found := hasSuffixes(s[n:], "cd"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("cd") + } + + return nil +} + +// Well known LuminousIntensity constants. +const ( + // Candela is a unit of luminous intensity. cd + NanoCandela LuminousIntensity = 1 + MicroCandela LuminousIntensity = 1000 * NanoCandela + MilliCandela LuminousIntensity = 1000 * MicroCandela + Candela LuminousIntensity = 1000 * MilliCandela + KiloCandela LuminousIntensity = 1000 * Candela + MegaCandela LuminousIntensity = 1000 * KiloCandela + GigaCandela LuminousIntensity = 1000 * MegaCandela + + maxLuminousIntensity = 9223372036854775807 * NanoCandela + minLuminousIntensity = -9223372036854775807 * NanoCandela +) + +// LuminousFlux is a measurement of total quantity of visible light energy +// emitted with wavelength power weighted by a luminosity function which +// represents a model of the human eye's response to different wavelengths. +// The CIE 1931 luminosity function is the standard for lumens. +// +// LuminousFlux is stored as nano lumens. +// +// The highest representable value is 9.2Glm. +type LuminousFlux int64 + +// String returns the energy formatted as a string in Lumens. +func (f LuminousFlux) String() string { + return nanoAsString(int64(f)) + "lm" +} + +// Set sets the LuminousFlux to the value represented by s. Units are to +// be provided in "lm" with an optional SI prefix: "p", "n", "u", "µ", "m", "k", +// "M", "G" or "T". +func (f *LuminousFlux) Set(s string) error { + v, n, err := valueOfUnitString(s, nano) + if err != nil { + if e, ok := err.(*parseError); ok { + switch e.error { + case errNotANumber: + if found := hasSuffixes(s, "lm"); found != "" { + return err + } + return notNumberUnitErr("lm") + case errOverflowsInt64: + return maxValueErr(maxLuminousFlux.String()) + case errOverflowsInt64Negative: + return minValueErr(minLuminousFlux.String()) + } + } + return err + } + + switch s[n:] { + case "lm": + *f = (LuminousFlux)(v) + case "": + return noUnitErr("lm") + default: + if found := hasSuffixes(s[n:], "lm"); found != "" { + return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T") + } + return incorrectUnitErr("lm") + } + + return nil +} + +// Well known LuminousFlux constants. +const ( + // Lumen is a unit of luminous flux. cd⋅sr + NanoLumen LuminousFlux = 1 + MicroLumen LuminousFlux = 1000 * NanoLumen + MilliLumen LuminousFlux = 1000 * MicroLumen + Lumen LuminousFlux = 1000 * MilliLumen + KiloLumen LuminousFlux = 1000 * Lumen + MegaLumen LuminousFlux = 1000 * KiloLumen + GigaLumen LuminousFlux = 1000 * MegaLumen + + maxLuminousFlux = 9223372036854775807 * NanoLumen + minLuminousFlux = -9223372036854775807 * NanoLumen +) + +// + +func prefixZeros(digits, v int) string { + // digits is expected to be around 2~3. + s := strconv.Itoa(v) + for len(s) < digits { + // O(n²) but since digits is expected to run 2~3 times at most, it doesn't + // matter. + s = "0" + s + } + return s +} + +// nanoAsString converts a value in S.I. unit in a string with the predefined +// prefix. +func nanoAsString(v int64) string { + sign := "" + if v < 0 { + if v == -9223372036854775808 { + v++ + } + sign = "-" + v = -v + } + var frac int + var base int + var precision int64 + unit := "" + switch { + case v >= 999999500000000001: + precision = v % 1000000000000000 + base = int(v / 1000000000000000) + if precision > 500000000000000 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "G" + case v >= 999999500000001: + precision = v % 1000000000000 + base = int(v / 1000000000000) + if precision > 500000000000 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "M" + case v >= 999999500001: + precision = v % 1000000000 + base = int(v / 1000000000) + if precision > 500000000 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "k" + case v >= 999999501: + precision = v % 1000000 + base = int(v / 1000000) + if precision > 500000 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "" + case v >= 1000000: + precision = v % 1000 + base = int(v / 1000) + if precision > 500 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "m" + case v >= 1000: + frac = int(v) % 1000 + base = int(v) / 1000 + unit = "µ" + default: + if v == 0 { + return "0" + } + base = int(v) + unit = "n" + } + if frac == 0 { + return sign + strconv.Itoa(base) + unit + } + return sign + strconv.Itoa(base) + "." + prefixZeros(3, frac) + unit +} + +// microAsString converts a value in S.I. unit in a string with the predefined +// prefix. +func microAsString(v int64) string { + sign := "" + if v < 0 { + if v == -9223372036854775808 { + v++ + } + sign = "-" + v = -v + } + var frac int + var base int + var precision int64 + unit := "" + switch { + case v >= 999999500000000001: + precision = v % 1000000000000000 + base = int(v / 1000000000000000) + if precision > 500000000000000 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "T" + case v >= 999999500000001: + precision = v % 1000000000000 + base = int(v / 1000000000000) + if precision > 500000000000 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "G" + case v >= 999999500001: + precision = v % 1000000000 + base = int(v / 1000000000) + if precision > 500000000 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "M" + case v >= 999999501: + precision = v % 1000000 + base = int(v / 1000000) + if precision > 500000 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "k" + case v >= 1000000: + precision = v % 1000 + base = int(v / 1000) + if precision > 500 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "" + case v >= 1000: + frac = int(v) % 1000 + base = int(v) / 1000 + unit = "m" + default: + if v == 0 { + return "0" + } + base = int(v) + unit = "µ" + } + if frac == 0 { + return sign + strconv.Itoa(base) + unit + } + return sign + strconv.Itoa(base) + "." + prefixZeros(3, frac) + unit +} + +// picoAsString converts a value in S.I. unit in a string with the predefined +// prefix. +func picoAsString(v int64) string { + sign := "" + if v < 0 { + if v == -9223372036854775808 { + v++ + } + sign = "-" + v = -v + } + var frac int + var base int + var precision int64 + unit := "" + switch { + case v >= 999999500000000001: + precision = v % 1000000000000000 + base = int(v / 1000000000000000) + if precision > 500000000000000 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "M" + case v >= 999999500000001: + precision = v % 1000000000000 + base = int(v / 1000000000000) + if precision > 500000000000 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "k" + case v >= 999999500001: + precision = v % 1000000000 + base = int(v / 1000000000) + if precision > 500000000 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "" + case v >= 999999501: + precision = v % 1000000 + base = int(v / 1000000) + if precision > 500000 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "m" + case v >= 1000000: + precision = v % 1000 + base = int(v / 1000) + if precision > 500 { + base++ + } + frac = (base % 1000) + base = base / 1000 + unit = "µ" + case v >= 1000: + frac = int(v) % 1000 + base = int(v) / 1000 + unit = "n" + default: + if v == 0 { + return "0" + } + base = int(v) + unit = "p" + } + if frac == 0 { + return sign + strconv.Itoa(base) + unit + } + return sign + strconv.Itoa(base) + "." + prefixZeros(3, frac) + unit +} + +// Decimal is the representation of decimal number. +type decimal struct { + // base hold the significant digits. + base uint64 + // exponent is the left or right decimal shift. (powers of ten). + exp int + // neg it true if the number is negative. + neg bool +} + +// Positive powers of 10 in the form such that powerOF10[index] = 10^index. +var powerOf10 = [...]uint64{ + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, +} + +// Maximum value for a int64. +const maxInt64 = (1 << 63) - 1 + +var maxInt64Str = "9223372036854775807" + +var ( + errOverflowsInt64 = errors.New("exceeds maximum") + errOverflowsInt64Negative = errors.New("exceeds minimum") + errNotANumber = errors.New("not a number") +) + +// Converts from decimal to int64. +// +// Scale is combined with the decimal exponent to maximise the resolution and is +// in powers of ten. +// +// Returns true if the value overflowed. +func dtoi(d decimal, scale int) (int64, bool) { + // Get the total magnitude of the number. + // a^x * b^y = a*b^(x+y) since scale is of the order unity this becomes + // 1^x * b^y = b^(x+y). + // mag must be positive to use as index in to powerOf10 array. + u := d.base + mag := d.exp + scale + if mag < 0 { + mag = -mag + } + var n int64 + if mag > 18 { + return 0, true + } + // Divide is = 10^(-mag) + switch { + case d.exp+scale < 0: + u = (u + powerOf10[mag]/2) / powerOf10[mag] + break + case mag == 0: + if u > maxInt64 { + return 0, true + } + break + default: + check := u * powerOf10[mag] + if check/powerOf10[mag] != u || check > maxInt64 { + return 0, true + } + u *= powerOf10[mag] + } + + n = int64(u) + if d.neg { + n = -n + } + return n, false +} + +// Converts a string to a decimal form. The return int is how many bytes of the +// string are considered numeric. The string may contain +-0 prefixes and +// arbitrary suffixes as trailing non number characters are ignored. +// Significant digits are stored without leading or trailing zeros, rather a +// base and exponent is used. Significant digits are stored as uint64, max size +// of significant digits is int64 +func atod(s string) (decimal, int, error) { + var d decimal + start := 0 + dp := 0 + end := len(s) + seenDigit := false + seenZero := false + isPoint := false + seenPlus := false + + // Strip leading zeros, +/- and mark DP. + for i := 0; i < len(s); i++ { + switch { + case s[i] == '-': + if seenDigit { + end = i + break + } + if seenPlus { + return decimal{}, 0, &parseError{ + errors.New("contains both plus and minus symbols"), + } + } + if d.neg { + return decimal{}, 0, &parseError{ + errors.New("contains multiple minus symbols"), + } + } + d.neg = true + start++ + case s[i] == '+': + if seenDigit { + end = i + break + } + if d.neg { + return decimal{}, 0, &parseError{ + errors.New("contains both plus and minus symbols"), + } + } + if seenPlus { + return decimal{}, 0, &parseError{ + errors.New("contains multiple plus symbols"), + } + } + seenPlus = true + start++ + case s[i] == '.': + if isPoint { + return decimal{}, 0, &parseError{ + errors.New("contains multiple decimal points"), + } + } + isPoint = true + dp = i + if !seenDigit { + start++ + } + case s[i] == '0': + if !seenDigit { + start++ + } + seenZero = true + case s[i] >= '1' && s[i] <= '9': + seenDigit = true + default: + if !seenDigit && !seenZero { + return decimal{}, 0, &parseError{errNotANumber} + } + end = i + break + } + } + + last := end + seenDigit = false + exp := 0 + // Strip non significant zeros to find base exponent. + for i := end - 1; i > start-1; i-- { + switch { + case s[i] >= '1' && s[i] <= '9': + seenDigit = true + case s[i] == '.': + if !seenDigit { + end-- + } + case s[i] == '0': + if !seenDigit { + if i > dp { + end-- + } + if i <= dp || dp == 0 { + exp++ + } + } + default: + last-- + end-- + } + } + + for i := start; i < end; i++ { + c := s[i] + // Check that is is a digit. + if c >= '0' && c <= '9' { + // *10 is decimal shift left. + d.base *= 10 + // Convert ascii digit into number. + check := d.base + uint64(c-'0') + // Check should always be larger than u unless we have overflowed. + // Similarly if check > max it will overflow when converted to int64. + if check < d.base || check > maxInt64 { + if d.neg { + return decimal{}, 0, &parseError{errOverflowsInt64Negative} + } + return decimal{}, 0, &parseError{errOverflowsInt64} + } + d.base = check + } else if c != '.' { + return decimal{}, 0, &parseError{errNotANumber} + } + } + if !isPoint { + d.exp = exp + } else { + if dp > start && dp < end { + // Decimal Point is in the middle of a number. + end-- + } + // Find the exponent based on decimal point distance from left and the + // length of the number. + d.exp = (dp - start) - (end - start) + if dp <= start { + // Account for numbers of the form 1 > n < -1 eg 0.0001. + d.exp++ + } + } + return d, last, nil +} + +// valueOfUnitString is a helper for converting a string and a prefix in to a +// physic unit. It can be used when characters of the units do not conflict with +// any of the SI prefixes. +func valueOfUnitString(s string, base prefix) (int64, int, error) { + d, n, err := atod(s) + if err != nil { + return 0, n, err + } + si := prefix(unit) + if n != len(s) { + r, rsize := utf8.DecodeRuneInString(s[n:]) + if r <= 1 || rsize == 0 { + return 0, 0, &parseError{ + errors.New("unexpected end of string"), + } + } + var siSize int + si, siSize = parseSIPrefix(r) + n += siSize + } + v, overflow := dtoi(d, int(si-base)) + if overflow { + if d.neg { + return -maxInt64, 0, &parseError{errOverflowsInt64Negative} + } + return maxInt64, 0, &parseError{errOverflowsInt64} + } + return v, n, nil +} + +// decimalMul calcululates the product of two decimals; a and b, keeping the +// base less than maxInt64. Returns the number of times a figure was trimmed +// from either base coefficients. This function is to aid in the multiplication +// of numbers whose product have more than 18 significant figures. The minimum +// accuracy of the end product that has been truncated is 9 significant figures. +func decimalMul(a, b decimal) (decimal, uint) { + switch { + case a.base == 0 || b.base == 0: + // Anything multiplied by zero is zero. Special case to set exponent to + // zero. + return decimal{}, 0 + case a.base > (1<<64)-6 || b.base > (1<<64)-6: + // In normal usage base will never be greater than 1<<63. However since + // base could be large as (1<<64 -1) this is to prevent an infinite loop + // when ((1<<64)-6)+5 overflows in the truncate least significant digit + // loop during rounding without adding addition bounds checking at that + // point. + break + default: + exp := a.exp + b.exp + neg := a.neg != b.neg + ab := a.base + bb := b.base + for i := uint(0); i < 21; i++ { + if ab <= 1 || bb <= 1 { + // This will always fit inside uint64. + return decimal{ab * bb, exp, neg}, i + } + if base := ab * bb; (base/ab == bb) && base < maxInt64 { + // Return if product did not overflow or exceed int64. + return decimal{base, exp, neg}, i + } + // Truncate least significant digit in product. + if bb > ab { + bb = (bb + 5) / 10 + // Compact trailing zeros if any. + for bb > 0 && bb%10 == 0 { + bb /= 10 + exp++ + } + } else { + ab = (ab + 5) / 10 + // Compact trailing zeros if any. + for ab > 0 && ab%10 == 0 { + ab /= 10 + exp++ + } + } + exp++ + } + } + return decimal{}, 21 +} + +// hasSuffixes returns the first suffix found and the prefix content. +func hasSuffixes(s string, suffixes ...string) string { + for _, suffix := range suffixes { + if strings.HasSuffix(s, suffix) { + return suffix + } + } + return "" +} + +type parseError struct { + error +} + +func noUnitErr(valid string) error { + return errors.New("no unit provided; need " + valid) +} + +func incorrectUnitErr(valid string) error { + return errors.New("unknown unit provided; need " + valid) +} + +func unknownUnitPrefixErr(unit, valid string) error { + return errors.New("unknown unit prefix; valid prefixes for \"" + unit + "\" are " + valid) +} + +func maxValueErr(valid string) error { + return errors.New("maximum value is " + valid) +} + +func minValueErr(valid string) error { + return errors.New("minimum value is " + valid) +} + +func notNumberUnitErr(unit string) error { + return errors.New("does not contain number or unit " + unit) +} + +type prefix int + +const ( + pico prefix = -12 + nano prefix = -9 + micro prefix = -6 + milli prefix = -3 + unit prefix = 0 + deca prefix = 1 + hecto prefix = 2 + kilo prefix = 3 + mega prefix = 6 + giga prefix = 9 + tera prefix = 12 +) + +func parseSIPrefix(r rune) (prefix, int) { + switch r { + case 'p': + return pico, len("p") + case 'n': + return nano, len("n") + case 'u': + return micro, len("u") + case 'µ': + return micro, len("µ") + case 'm': + return milli, len("m") + case 'k': + return kilo, len("k") + case 'M': + return mega, len("M") + case 'G': + return giga, len("G") + case 'T': + return tera, len("T") + default: + return unit, 0 + } +} diff --git a/vendor/periph.io/x/periph/conn/pin/func.go b/vendor/periph.io/x/periph/conn/pin/func.go new file mode 100644 index 0000000..3ebd2ce --- /dev/null +++ b/vendor/periph.io/x/periph/conn/pin/func.go @@ -0,0 +1,58 @@ +// Copyright 2018 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 pin + +import ( + "strconv" + "strings" +) + +// Func is a pin function. +// +// The Func format must be "[A-Z]+", "[A-Z]+_[A-Z]+" or exceptionally +// "(In|Out)/(Low|High)". +type Func string + +// FuncNone is returned by PinFunc.Func() for a Pin without an active +// functionality. +const FuncNone Func = "" + +// Specialize converts a "BUS_LINE" function and appends the bug number and +// line number, to look like "BUS0_LINE1". +// +// Use -1 to not add a bus or line number. +func (f Func) Specialize(b, l int) Func { + if f == FuncNone { + return FuncNone + } + if b != -1 { + parts := strings.SplitN(string(f), "_", 2) + if len(parts) == 1 { + return FuncNone + } + f = Func(parts[0] + strconv.Itoa(b) + "_" + parts[1]) + } + if l != -1 { + f += Func(strconv.Itoa(l)) + } + return f +} + +// Generalize is the reverse of Specialize(). +func (f Func) Generalize() Func { + parts := strings.SplitN(string(f), "_", 2) + f = Func(strings.TrimRightFunc(parts[0], isNum)) + if len(parts) == 2 { + f += "_" + f += Func(strings.TrimRightFunc(parts[1], isNum)) + } + return f +} + +// + +func isNum(r rune) bool { + return r >= '0' && r <= '9' +} diff --git a/vendor/periph.io/x/periph/conn/pin/pin.go b/vendor/periph.io/x/periph/conn/pin/pin.go new file mode 100644 index 0000000..9cf60bd --- /dev/null +++ b/vendor/periph.io/x/periph/conn/pin/pin.go @@ -0,0 +1,139 @@ +// Copyright 2016 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 pin declare well known pins. +// +// pin is about physical pins, not about their logical function. +// +// While not a protocol strictly speaking, these are "well known constants". +package pin + +import ( + "errors" + + "periph.io/x/periph/conn" +) + +// These are well known pins. +var ( + INVALID *BasicPin // Either floating or invalid pin + GROUND *BasicPin // Ground + V1_8 *BasicPin // 1.8V (filtered) + V2_8 *BasicPin // 2.8V (filtered) + V3_3 *BasicPin // 3.3V (filtered) + V5 *BasicPin // 5V (filtered) + DC_IN *BasicPin // DC IN; this is normally the 5V input + BAT_PLUS *BasicPin // LiPo Battery + connector +) + +// Pin is the minimal common interface shared between gpio.PinIO and +// analog.PinIO. +type Pin interface { + conn.Resource + // Name returns the name of the pin. + Name() string + // Number returns the logical pin number or a negative number if the pin is + // not a GPIO, e.g. GROUND, V3_3, etc. + Number() int + // Function returns a user readable string representation of what the pin is + // configured to do. Common case is In and Out but it can be bus specific pin + // name. + // + // Deprecated: Use PinFunc.Func. Will be removed in v4. + Function() string +} + +// PinFunc is a supplementary interface that enables specifically querying for +// the pin function. +// +// TODO(maruel): It will be merged into interface Pin for v4. +type PinFunc interface { + // Func returns the pin's current function. + // + // The returned value may be specialized or generalized, depending on the + // actual port. For example it will likely be generalized for ports served + // over USB (like a FT232H with D0 set as SPI_MOSI) but specialized for + // ports on the base board (like a RPi3 with GPIO10 set as SPI0_MOSI). + Func() Func + // SupportedFuncs returns the possible functions this pin support. + // + // Do not mutate the returned slice. + SupportedFuncs() []Func + // SetFunc sets the pin function. + // + // Example use is to reallocate a RPi3's GPIO14 active function between + // UART0_TX and UART1_TX. + SetFunc(f Func) error +} + +// + +// BasicPin implements Pin as a static pin. +// +// It doesn't have a usable functionality. +type BasicPin struct { + N string +} + +// String implements conn.Resource. +func (b *BasicPin) String() string { + return b.N +} + +// Halt implements conn.Resource. +func (b *BasicPin) Halt() error { + return nil +} + +// Name implements Pin. +func (b *BasicPin) Name() string { + return b.N +} + +// Number implements Pin. +// +// Returns -1 as pin number. +func (b *BasicPin) Number() int { + return -1 +} + +// Function implements Pin. +// +// Returns "" as pin function. +func (b *BasicPin) Function() string { + return "" +} + +// Func implements PinFunc. +// +// Returns FuncNone as pin function. +func (b *BasicPin) Func() Func { + return FuncNone +} + +// SupportedFuncs implements PinFunc. +// +// Returns nil. +func (b *BasicPin) SupportedFuncs() []Func { + return nil +} + +// SetFunc implements PinFunc. +func (b *BasicPin) SetFunc(f Func) error { + return errors.New("pin: can't change static pin function") +} + +func init() { + INVALID = &BasicPin{N: "INVALID"} + GROUND = &BasicPin{N: "GROUND"} + V1_8 = &BasicPin{N: "1.8V"} + V2_8 = &BasicPin{N: "2.8V"} + V3_3 = &BasicPin{N: "3.3V"} + V5 = &BasicPin{N: "5V"} + DC_IN = &BasicPin{N: "DC_IN"} + BAT_PLUS = &BasicPin{N: "BAT+"} +} + +var _ Pin = INVALID +var _ PinFunc = INVALID diff --git a/vendor/periph.io/x/periph/conn/pin/pinreg/doc.go b/vendor/periph.io/x/periph/conn/pin/pinreg/doc.go new file mode 100644 index 0000000..bed7586 --- /dev/null +++ b/vendor/periph.io/x/periph/conn/pin/pinreg/doc.go @@ -0,0 +1,7 @@ +// Copyright 2016 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 pinreg is a registry for the physical headers (made up of pins) on +// a host. +package pinreg diff --git a/vendor/periph.io/x/periph/conn/pin/pinreg/pinreg.go b/vendor/periph.io/x/periph/conn/pin/pinreg/pinreg.go new file mode 100644 index 0000000..7ccdbfb --- /dev/null +++ b/vendor/periph.io/x/periph/conn/pin/pinreg/pinreg.go @@ -0,0 +1,148 @@ +// Copyright 2016 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 pinreg + +import ( + "errors" + "strconv" + "sync" + + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/gpio/gpioreg" + "periph.io/x/periph/conn/pin" +) + +// All contains all the on-board headers on a micro computer. +// +// The map key is the header name, e.g. "P1" or "EULER" and the value is a +// slice of slice of pin.Pin. For a 2x20 header, it's going to be a slice of +// [20][2]pin.Pin. +func All() map[string][][]pin.Pin { + mu.Lock() + defer mu.Unlock() + out := make(map[string][][]pin.Pin, len(allHeaders)) + for k, v := range allHeaders { + outV := make([][]pin.Pin, len(v)) + for i, w := range v { + outW := make([]pin.Pin, len(w)) + copy(outW, w) + outV[i] = outW + } + out[k] = outV + } + return out +} + +// Position returns the position on a pin if found. +// +// The header and the pin number. Pin numbers are 1-based. +// +// Returns "", 0 if not connected. +func Position(p pin.Pin) (string, int) { + mu.Lock() + defer mu.Unlock() + pos, _ := byPin[realPin(p).Name()] + return pos.name, pos.number +} + +// IsConnected returns true if the pin is on a header. +func IsConnected(p pin.Pin) bool { + _, i := Position(p) + return i != 0 +} + +// Register registers a physical header. +// +// It automatically registers all gpio pins to gpioreg. +func Register(name string, allPins [][]pin.Pin) error { + mu.Lock() + defer mu.Unlock() + if _, ok := allHeaders[name]; ok { + return errors.New("pinreg: header " + strconv.Quote(name) + " was already registered") + } + for i, line := range allPins { + for j, pin := range line { + if pin == nil || len(pin.Name()) == 0 { + return errors.New("pinreg: invalid pin on header " + name + "[" + strconv.Itoa(i+1) + "][" + strconv.Itoa(j+1) + "]") + } + } + } + allHeaders[name] = allPins + number := 1 + for _, line := range allPins { + for _, p := range line { + byPin[realPin(p).Name()] = position{name, number} + number++ + } + } + + count := 0 + for _, row := range allPins { + for _, p := range row { + count++ + if _, ok := p.(gpio.PinIO); ok { + if err := gpioreg.RegisterAlias(name+"_"+strconv.Itoa(count), p.Name()); err != nil { + // Unregister as much as possible. + _ = unregister(name) + return errors.New("pinreg: " + err.Error()) + } + } + } + } + + return nil +} + +// Unregister removes a previously registered header. +// +// This can happen when an USB device, which exposed an header, is unplugged. +// This is also useful for unit testing. +func Unregister(name string) error { + mu.Lock() + defer mu.Unlock() + return unregister(name) +} + +// + +type position struct { + name string // Header name + number int // Pin number +} + +var ( + mu sync.Mutex + allHeaders = map[string][][]pin.Pin{} // every known headers as per internal lookup table + byPin = map[string]position{} // GPIO pin name to position +) + +func unregister(name string) error { + if hdr, ok := allHeaders[name]; ok { + var err error + delete(allHeaders, name) + count := 0 + for _, row := range hdr { + for _, p := range row { + count++ + if _, ok := p.(gpio.PinIO); ok { + if err1 := gpioreg.Unregister(name + "_" + strconv.Itoa(count)); err1 != nil && err == nil { + // Continue unregistering as much as possible. + err = errors.New("pinreg: " + err1.Error()) + } + } + } + } + return err + } + return errors.New("pinreg: can't unregister unknown header name " + strconv.Quote(name)) +} + +// realPin returns the real pin from an alias. +func realPin(p pin.Pin) pin.Pin { + if r, ok := p.(gpio.RealPin); ok { + p = r.Real() + } + return p +} diff --git a/vendor/periph.io/x/periph/conn/spi/func.go b/vendor/periph.io/x/periph/conn/spi/func.go new file mode 100644 index 0000000..6b9a2ea --- /dev/null +++ b/vendor/periph.io/x/periph/conn/spi/func.go @@ -0,0 +1,15 @@ +// Copyright 2018 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 spi + +import "periph.io/x/periph/conn/pin" + +// Well known pin functionality. +const ( + CLK pin.Func = "SPI_CLK" // Clock + CS pin.Func = "SPI_CS" // Chip select + MISO pin.Func = "SPI_MISO" // Master in + MOSI pin.Func = "SPI_MOSI" // Master out +) diff --git a/vendor/periph.io/x/periph/conn/spi/spi.go b/vendor/periph.io/x/periph/conn/spi/spi.go new file mode 100644 index 0000000..b1a2159 --- /dev/null +++ b/vendor/periph.io/x/periph/conn/spi/spi.go @@ -0,0 +1,187 @@ +// Copyright 2016 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 spi defines the API to communicate with devices over the SPI +// protocol. +// +// As described in https://periph.io/x/periph/conn#hdr-Concepts, periph.io uses +// the concepts of Bus, Port and Conn. +// +// In the package spi, 'Bus' is not exposed, as it would be SPI bus number +// without a CS line, for example on linux asking for "/dev/spi0" without the +// ".0" suffix. +// +// The OS doesn't allow that so it is counter productive to express this at the +// API layer, so 'Port' is exposed directly instead. +// +// Use Port.Connect() converts the uninitialized Port into a Conn. +// +// See https://en.wikipedia.org/wiki/Serial_Peripheral_Interface for more +// information. +package spi + +import ( + "io" + "strconv" + + "periph.io/x/periph/conn" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/physic" +) + +// Mode determines how communication is done. +// +// The bits can be OR'ed to change the parameters used for +// communication. +// +type Mode int + +// Mode determines the SPI communication parameters. +// +// CPOL means the clock polarity. Idle is High when set. +// +// CPHA is the clock phase, sample on trailing edge when set. +const ( + Mode0 Mode = 0x0 // CPOL=0, CPHA=0 + Mode1 Mode = 0x1 // CPOL=0, CPHA=1 + Mode2 Mode = 0x2 // CPOL=1, CPHA=0 + Mode3 Mode = 0x3 // CPOL=1, CPHA=1 + + // HalfDuplex specifies that MOSI and MISO use the same wire, and that only + // one duplex is used at a time. + HalfDuplex Mode = 0x4 + // NoCS request the driver to not use the CS line. + NoCS Mode = 0x8 + // LSBFirst requests the words to be encoded in little endian instead of the + // default big endian. + LSBFirst = 0x10 +) + +func (m Mode) String() string { + s := "" + switch m & Mode3 { + case Mode0: + s = "Mode0" + case Mode1: + s = "Mode1" + case Mode2: + s = "Mode2" + case Mode3: + s = "Mode3" + } + m &^= Mode3 + if m&HalfDuplex != 0 { + s += "|HalfDuplex" + } + m &^= HalfDuplex + if m&NoCS != 0 { + s += "|NoCS" + } + m &^= NoCS + if m&LSBFirst != 0 { + s += "|LSBFirst" + } + m &^= LSBFirst + if m != 0 { + s += "|0x" + s += strconv.FormatUint(uint64(m), 16) + } + return s +} + +// Packet represents one packet when sending multiple packets as a transaction. +type Packet struct { + // W and R are the output and input data. When HalfDuplex is specified to + // Connect, only one of the two can be set. + W, R []byte + // BitsPerWord overrides the default bits per word value set in Connect. + BitsPerWord uint8 + // KeepCS tells the driver to keep CS asserted after this packet is + // completed. This can be leveraged to create long transaction as multiple + // packets like to use 9 bits commands then 8 bits data. + // + // Normally during a spi.Conn.TxPackets() call, KeepCS should be set to true + // for all packets except the last one. If the last one is set to true, the + // CS line stays asserted, leaving the transaction hanging on the bus. + // + // KeepCS is ignored when NoCS was specified to Connect. + KeepCS bool +} + +// Conn defines the interface a concrete SPI driver must implement. +// +// Implementers can optionally implement io.Writer and io.Reader for +// unidirectional operation. +type Conn interface { + conn.Conn + // TxPackets does multiple operations over the SPI connection. + // + // The maximum number of bytes can be limited depending on the driver. Query + // conn.Limits.MaxTxSize() can be used to determine the limit. + // + // If the last packet has KeepCS:true, the CS line stays asserted. This + // enables doing SPI transaction over multiple calls. + // + // Conversely, if any packet beside the last one has KeepCS:false, the CS + // line will blip for a short amount of time to force a new transaction. + // + // It was observed on RPi3 hardware to have a one clock delay between each + // packet. + TxPackets(p []Packet) error +} + +// Port is the interface to be provided to device drivers. +// +// The device driver, that is the driver for the peripheral connected over +// this port, calls Connect() to retrieve a configured connection as Conn. +type Port interface { + String() string + // Connect sets the communication parameters of the connection for use by a + // device. + // + // The device driver must call this function exactly once. + // + // f must specify the maximum rated speed by the device's spec. The lowest + // speed between the port speed and the device speed is selected. Use 0 for f + // if there is no known maximum value for this device. + // + // mode specifies the clock and signal polarities, if the port is using half + // duplex (shared MISO and MOSI) or if CS is not needed. + // + // bits is the number of bits per word. Generally you should use 8. + Connect(f physic.Frequency, mode Mode, bits int) (Conn, error) +} + +// PortCloser is a SPI port that can be closed. +// +// This interface is meant to be handled by the application. +type PortCloser interface { + io.Closer + Port + // LimitSpeed sets the maximum port speed. + // + // It lets an application use a device at a lower speed than the maximum + // speed as rated by the device driver. This is useful for example when the + // wires are long or the connection is of poor quality. + // + // This function can be called multiple times and resets the previous value. + // 0 is not a valid value for f. The lowest speed between the port speed and + // the device speed is selected. + LimitSpeed(f physic.Frequency) error +} + +// Pins defines the pins that a SPI port interconnect is using on the host. +// +// It is expected that a implementer of ConnCloser or Conn also implement Pins +// but this is not a requirement. +type Pins interface { + // CLK returns the SCK (clock) pin. + CLK() gpio.PinOut + // MOSI returns the SDO (master out, slave in) pin. + MOSI() gpio.PinOut + // MISO returns the SDI (master in, slave out) pin. + MISO() gpio.PinIn + // CS returns the CSN (chip select) pin. + CS() gpio.PinOut +} diff --git a/vendor/periph.io/x/periph/conn/spi/spireg/spireg.go b/vendor/periph.io/x/periph/conn/spi/spireg/spireg.go new file mode 100644 index 0000000..dbc74f7 --- /dev/null +++ b/vendor/periph.io/x/periph/conn/spi/spireg/spireg.go @@ -0,0 +1,262 @@ +// 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 +} diff --git a/vendor/periph.io/x/periph/experimental/devices/pca9685/doc.go b/vendor/periph.io/x/periph/experimental/devices/pca9685/doc.go new file mode 100644 index 0000000..5962a4b --- /dev/null +++ b/vendor/periph.io/x/periph/experimental/devices/pca9685/doc.go @@ -0,0 +1,16 @@ +// Copyright 2018 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 pca9685 includes utilities to controls pca9685 module and servo motors. +// +// More details +// +// Datasheet +// +// https://www.nxp.com/docs/en/data-sheet/PCA9685.pdf +// +// Product page: +// +// https://www.nxp.com/products/analog/interfaces/ic-bus/ic-led-controllers/16-channel-12-bit-pwm-fm-plus-ic-bus-led-controller:PCA9685 +package pca9685 diff --git a/vendor/periph.io/x/periph/experimental/devices/pca9685/pca9685.go b/vendor/periph.io/x/periph/experimental/devices/pca9685/pca9685.go new file mode 100644 index 0000000..cd719cb --- /dev/null +++ b/vendor/periph.io/x/periph/experimental/devices/pca9685/pca9685.go @@ -0,0 +1,138 @@ +// Copyright 2018 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 pca9685 + +import ( + "time" + + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/i2c" + "periph.io/x/periph/conn/physic" +) + +// I2CAddr i2c default address. +const I2CAddr uint16 = 0x40 + +// PCA9685 registers. +const ( + mode1 byte = 0x00 + mode2 byte = 0x01 + prescale byte = 0xFE + // Each channel has two 12-bit registers (on & off time). + led0OnL byte = 0x06 // Start address for setting channel 0. + allLedOnL byte = 0xFA // Start address for setting all channels. +) + +// Mode register 1, mode1. +const ( + restart byte = 0x80 + ai byte = 0x20 // Auto-increment register after each read and write. + sleep byte = 0x10 + allCall byte = 0x01 +) + +// Mode register 2, mode2. +const ( + invrt byte = 0x10 + outDrv byte = 0x04 +) + +// Dev is a handler to pca9685 controller. +type Dev struct { + dev *i2c.Dev +} + +// NewI2C returns a Dev object that communicates over I2C. +// +// To use on the default address, pca9685.I2CAddr must be passed as argument. +func NewI2C(bus i2c.Bus, address uint16) (*Dev, error) { + dev := &Dev{ + dev: &i2c.Dev{Bus: bus, Addr: address}, + } + err := dev.init() + if err != nil { + return nil, err + } + + return dev, nil +} + +func (d *Dev) init() error { + if err := d.SetAllPwm(0, 0); err != nil { + return err + } + + if _, err := d.dev.Write([]byte{mode2, outDrv}); err != nil { + return err + } + if _, err := d.dev.Write([]byte{mode1, allCall}); err != nil { + return err + } + + time.Sleep(100 * time.Millisecond) + + modeRead := [1]byte{} + if err := d.dev.Tx([]byte{mode1}, modeRead[:]); err != nil { + return err + } + + mode := (modeRead[0] & ^sleep) | ai + if _, err := d.dev.Write([]byte{mode1, mode}); err != nil { + return err + } + + time.Sleep(5 * time.Millisecond) + + return d.SetPwmFreq(50 * physic.Hertz) +} + +// SetPwmFreq set the PWM frequency. +func (d *Dev) SetPwmFreq(freqHz physic.Frequency) error { + p := (25*physic.MegaHertz/4096 + freqHz/2) / freqHz + + modeRead := [1]byte{} + if err := d.dev.Tx([]byte{mode1}, modeRead[:]); err != nil { + return err + } + + oldmode := modeRead[0] + if _, err := d.dev.Write([]byte{mode1, (oldmode & ^restart) | sleep}); err != nil { + return err + } + if _, err := d.dev.Write([]byte{prescale, byte(p)}); err != nil { + return err + } + if _, err := d.dev.Write([]byte{mode1, oldmode}); err != nil { + return err + } + + time.Sleep(100 * time.Millisecond) + + _, err := d.dev.Write([]byte{mode1, oldmode | restart}) + return err +} + +// setPWM writes a PWM value in a specific register. +func (d *Dev) setPWM(register uint8, on, off gpio.Duty) error { + // Chained writes are possible due to auto-increment. + _, err := d.dev.Write([]byte{ + register, + byte(on), + byte(on >> 8), + byte(off), + byte(off >> 8), + }) + return err +} + +// SetAllPwm set a PWM value for all outputs. +func (d *Dev) SetAllPwm(on, off gpio.Duty) error { + return d.setPWM(allLedOnL, on, off) +} + +// SetPwm set a PWM value for a given PCA9685 channel. +func (d *Dev) SetPwm(channel int, on, off gpio.Duty) error { + return d.setPWM(led0OnL+byte(4*channel), on, off) +} diff --git a/vendor/periph.io/x/periph/experimental/devices/pca9685/servo.go b/vendor/periph.io/x/periph/experimental/devices/pca9685/servo.go new file mode 100644 index 0000000..73b3410 --- /dev/null +++ b/vendor/periph.io/x/periph/experimental/devices/pca9685/servo.go @@ -0,0 +1,90 @@ +// Copyright 2018 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 pca9685 + +import ( + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/physic" +) + +// ServoGroup a group of servos connected to a pca9685 module +type ServoGroup struct { + *Dev + minPwm gpio.Duty + maxPwm gpio.Duty + minAngle physic.Angle + maxAngle physic.Angle +} + +// Servo individual servo from a group of servos connected to a pca9685 module +type Servo struct { + group *ServoGroup + channel int + minAngle physic.Angle + maxAngle physic.Angle +} + +// NewServoGroup returns a servo group connected through the pca9685 module +// some pwm and angle limits can be set +func NewServoGroup(dev *Dev, minPwm, maxPwm gpio.Duty, minAngle, maxAngle physic.Angle) *ServoGroup { + return &ServoGroup{ + Dev: dev, + minPwm: minPwm, + maxPwm: maxPwm, + minAngle: minAngle, + maxAngle: maxAngle, + } +} + +// SetMinMaxPwm change pwm and angle limits +func (s *ServoGroup) SetMinMaxPwm(minAngle, maxAngle physic.Angle, minPwm, maxPwm gpio.Duty) { + s.maxPwm = maxPwm + s.minPwm = minPwm + s.minAngle = minAngle + s.maxAngle = maxAngle +} + +// SetAngle set an angle in a given channel of the servo group +func (s *ServoGroup) SetAngle(channel int, angle physic.Angle) error { + value := mapValue(int(angle), int(s.minAngle), int(s.maxAngle), int(s.minPwm), int(s.maxPwm)) + return s.Dev.SetPwm(channel, 0, gpio.Duty(value)) +} + +// GetServo returns a individual Servo to be controlled +func (s *ServoGroup) GetServo(channel int) *Servo { + return &Servo{ + group: s, + channel: channel, + minAngle: s.minAngle, + maxAngle: s.maxAngle, + } +} + +// SetMinMaxAngle change angle limits for the servo +func (s *Servo) SetMinMaxAngle(min, max physic.Angle) { + s.minAngle = min + s.maxAngle = max +} + +// SetAngle set an angle on the servo +// will consider the angle limits set +func (s *Servo) SetAngle(angle physic.Angle) error { + if angle < s.minAngle { + angle = s.minAngle + } + if angle > s.maxAngle { + angle = s.maxAngle + } + return s.group.SetAngle(s.channel, angle) +} + +// SetPwm set an pmw value to the servo +func (s *Servo) SetPwm(pwm gpio.Duty) error { + return s.group.SetPwm(s.channel, 0, pwm) +} + +func mapValue(x, inMin, inMax, outMin, outMax int) int { + return (x-inMin)*(outMax-outMin)/(inMax-inMin) + outMin +} diff --git a/vendor/periph.io/x/periph/host/allwinner/a20.go b/vendor/periph.io/x/periph/host/allwinner/a20.go new file mode 100644 index 0000000..fab6595 --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/a20.go @@ -0,0 +1,220 @@ +// Copyright 2018 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. + +// This file contains pin mapping information that is specific to the Allwinner +// A20 model. + +package allwinner + +import ( + "strings" + + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/host/sysfs" +) + +// mappingA20 describes the mapping of the A20 processor gpios to their +// alternate functions. +// +// It omits the in & out functions which are available on all gpio. +// +// The mapping comes from the datasheet page 241: +// http://dl.linux-sunxi.org/A20/A20%20User%20Manual%202013-03-22.pdf +var mappingA20 = map[string][5]pin.Func{ + "PA0": {"ERXD3", "SPI1_CS0", "UART2_RTS", "GRXD3"}, + "PA1": {"ERXD2", "SPI1_CLK", "UART2_CTS", "GRXD2"}, + "PA2": {"ERXD1", "SPI1_MOSI", "UART2_TX", "GRXD1"}, + "PA3": {"ERXD0", "SPI1_MISO", "UART2_RX", "GRXD0"}, + "PA4": {"ETXD3", "SPI1_CS1", "", "GTXD3"}, + "PA5": {"ETXD2", "SPI3_CS0", "", "GTXD2"}, + "PA6": {"ETXD1", "SPI3_CLK", "", "GTXD1"}, + "PA7": {"ETXD0", "SPI3_MOSI", "", "GTXD0"}, + "PA8": {"ERXCK", "SPI3_MISO", "", "CRXCK"}, + "PA9": {"ERXERR", "SPI3_CS1", "", "GNULL", "I2S1_MCLK"}, + "PA10": {"ERXDV", "", "UART1_TX", "GRXCTL"}, + "PA11": {"EMDC", "", "UART1_RX", "GMDC"}, + "PA12": {"EMDIO", "UART6_TX", "UART1_RTS", "GMDIO"}, + "PA13": {"ETXEN", "UART6_RX", "UART1_CTS", "GTXCTL"}, + "PA14": {"ETXCK", "UART7_TX", "UART1_DTR", "GNULL", "I2S1_SCK"}, + "PA15": {"ECRS", "UART7_RX", "UART1_DSR", "GTXCK", "I2S1_WS"}, + "PA16": {"ECOL", "CAN_TX", "UART1_DCD", "GCLKIN", "I2S1_DOUT"}, + "PA17": {"ETXERR", "CAN_RX", "UART1_RI", "GNULL", "I2S1_DIN"}, + "PB0": {"I2C0_SCL"}, + "PB1": {"I2C0_SDA"}, + "PB2": {"PWM0"}, + "PB3": {"IR0_TX", "", "SPDIF_MCLK", "", "STANBYWFI"}, + "PB4": {"IR0_RX"}, + "PB5": {"I2S0_MCLK", "AC97_MCLK"}, + "PB6": {"I2S0_SCK", "AC97_SCK"}, + "PB7": {"I2S0_WS", "AC97_SYNC"}, + "PB8": {"I2S0_DOUT0", "AC97_DOUT"}, + "PB9": {"I2S0_DOUT1"}, + "PB10": {"I2S0_DOUT2"}, + "PB11": {"I2S0_DOUT3"}, + "PB12": {"I2S0_DIN", "AC97_DI", "SPDIF_DI"}, + "PB13": {"SPI2_CS1", "", "SPDIF_DO"}, + "PB14": {"SPI2_CS0", "JTAG0_TMS"}, + "PB15": {"SPI2_CLK", "JTAG0_TCK"}, + "PB16": {"SPI2_MOSI", "JTAG0_TDO"}, + "PB17": {"SPI2_MISO", "JTAG0_TDI"}, + "PB18": {"I2C1_SCL"}, + "PB19": {"I2C1_SDA"}, + "PB20": {"I2C2_SCL"}, + "PB21": {"I2C2_SDA"}, + "PB22": {"UART0_TX", "IR1_TX"}, + "PB23": {"UART0_RX", "IR1_RX"}, + "PC0": {"NWE#", "SPI0_MOSI"}, + "PC1": {"NALE", "SPI0_MISO"}, + "PC2": {"NCLE", "SPI0_CLK"}, + "PC3": {"NCE1"}, + "PC4": {"NCE0"}, + "PC5": {"NRE#"}, + "PC6": {"NRB0", "SDC2_CMD"}, + "PC7": {"NRB1", "SDC2_CLK"}, + "PC8": {"NDQ0", "SDC2_D0"}, + "PC9": {"NDQ1", "SDC2_D1"}, + "PC10": {"NDQ2", "SDC2_D2"}, + "PC11": {"NDQ3", "SDC2_D3"}, + "PC12": {"NDQ4"}, + "PC13": {"NDQ5"}, + "PC14": {"NDQ6"}, + "PC15": {"NDQ7"}, + "PC16": {"NWP"}, + "PC17": {"NCE2"}, + "PC18": {"NCE3"}, + "PC19": {"NCE4", "SPI2_CS0", "", "", "PC_EINT12"}, + "PC20": {"NCE5", "SPI2_CLK", "", "", "PC_EINT13"}, + "PC21": {"NCE6", "SPI2_MOSI", "", "", "PC_EINT14"}, + "PC22": {"NCE7", "SPI2_MISO", "", "", "PC_EINT15"}, + "PC23": {"", "SPI2_CS0"}, + "PC24": {"NDQS"}, + "PD0": {"LCD0_D0", "LVDS0_VP0"}, + "PD1": {"LCD0_D1", "LVDS0_VN0"}, + "PD2": {"LCD0_D2", "LVDS0_VP1"}, + "PD3": {"LCD0_D3", "LVDS0_VN1"}, + "PD4": {"LCD0_D4", "LVDS0_VP2"}, + "PD5": {"LCD0_D5", "LVDS0_VN2"}, + "PD6": {"LCD0_D6", "LVDS0_VPC"}, + "PD7": {"LCD0_D7", "LVDS0_VNC"}, + "PD8": {"LCD0_D8", "LVDS0_VP3"}, + "PD9": {"LCD0_D9", "LVDS0_VN3"}, + "PD10": {"LCD0_D10", "LVDS1_VP0"}, + "PD11": {"LCD0_D11", "LVDS1_VN0"}, + "PD12": {"LCD0_D12", "LVDS1_VP1"}, + "PD13": {"LCD0_D13", "LVDS1_VN1"}, + "PD14": {"LCD0_D14", "LVDS1_VP2"}, + "PD15": {"LCD0_D15", "LVDS1_VN2"}, + "PD16": {"LCD0_D16", "LVDS1_VPC"}, + "PD17": {"LCD0_D17", "LVDS1_VNC"}, + "PD18": {"LCD0_D18", "LVDS1_VP3"}, + "PD19": {"LCD0_D19", "LVDS1_VN3"}, + "PD20": {"LCD0_D20", "CSI1_MCLK"}, + "PD21": {"LCD0_D21", "SMC_VPPEN"}, + "PD22": {"LCD0_D22", "SMC_VPPPP"}, + "PD23": {"LCD0_D23", "SMC_DET"}, + "PD24": {"LCD0_CLK", "SMC_VCCEN"}, + "PD25": {"LCD0_DE", "SMC_RST"}, + "PD26": {"LCD0_HSYNC", "SMC_SLK"}, + "PD27": {"LCD0_VSYNC", "SMC_SDA"}, + "PE0": {"TS0_CLK", "CSI0_PCLK"}, + "PE1": {"TS0_ERR", "CSI0_MCLK"}, + "PE2": {"TS0_SYNC", "CSI0_HSYNC"}, + "PE3": {"TS0_DLVD", "CSI0_VSYNC"}, + "PE4": {"TS0_D0", "CSI0_D0"}, + "PE5": {"TS0_D1", "CSI0_D1"}, + "PE6": {"TS0_D2", "CSI0_D2"}, + "PE7": {"TS0_D3", "CSI0_D3"}, + "PE8": {"TS0_D4", "CSI0_D4"}, + "PE9": {"TS0_D5", "CSI0_D5"}, + "PE10": {"TS0_D6", "CSI0_D6"}, + "PE11": {"TS0_D7", "CSI0_D7"}, + "PF0": {"SDC0_D1", "", "JTAG1_TMS"}, + "PF1": {"SDC0_D0", "", "JTAG1_TDI"}, + "PF2": {"SDC0_CLK", "", "UART0_TX"}, + "PF3": {"SDC0_CMD", "", "JTAG1_TDO"}, + "PF4": {"SDC0_D3", "", "UART0_RX"}, + "PF5": {"SDC0_D2", "", "JTAG1_TCK"}, + "PG0": {"TS1_CLK", "CSI1_PCLK", "SDC1_CMD"}, + "PG1": {"TS1_ERR", "CSI1_MCLK", "SDC1_CLK"}, + "PG2": {"TS1_SYNC", "CSI1_HSYNC", "SDC1_D0"}, + "PG3": {"TS1_DVLD", "CSI1_VSYNC", "SDC1_D1"}, + "PG4": {"TS1_D0", "CSI1_D0", "SDC1_D2", "CSI0_D8"}, + "PG5": {"TS1_D1", "CSI1_D1", "SDC1_D3", "CSI0_D9"}, + "PG6": {"TS1_D2", "CSI1_D2", "UART3_TX", "CSI0_D10"}, + "PG7": {"TS1_D3", "CSI1_D3", "UART3_RX", "CSI0_D11"}, + "PG8": {"TS1_D4", "CSI1_D4", "UART3_RTS", "CSI0_D12"}, + "PG9": {"TS1_D5", "CSI1_D4", "UART3_CTS", "CSI0_D13"}, + "PG10": {"TS1_D6", "CSI1_D6", "UART4_TX", "CSI0_D14"}, + "PG11": {"TS1_D7", "CSI1_D7", "UART4_RX", "CSI0_D15"}, + "PH0": {"LCD1_D0", "", "UART3_TX", "", "PH_EINT0"}, + "PH1": {"LCD1_D1", "", "UART3_RX", "", "PH_EINT1"}, + "PH2": {"LCD1_D2", "", "UART3_RTS", "", "PH_EINT2"}, + "PH3": {"LCD1_D3", "", "UART3_CTS", "", "PH_EINT3"}, + "PH4": {"LCD1_D4", "", "UART4_TX", "", "PH_EINT4"}, + "PH5": {"LCD1_D5", "", "UART4_RX", "", "PH_EINT5"}, + "PH6": {"LCD1_D6", "", "UART5_TX", "MS_BS", "PH_EINT6"}, + "PH7": {"LCD1_D7", "", "UART5_RX", "MS_CLK", "PH_EINT7"}, + "PH8": {"LCD1_D8", "ERXD3", "KP_IN0", "MS_D0", "PH_EINT8"}, + "PH9": {"LCD1_D9", "ERXD2", "KP_IN1", "MS_D1", "PH_EINT9"}, + "PH10": {"LCD1_D10", "ERXD1", "KP_IN2", "MS_D2", "PH_EINT10"}, + "PH11": {"LCD1_D11", "ERXD0", "KP_IN3", "MS_D3", "PH_EINT11"}, + "PH12": {"LCD1_D12", "", "PS2_SCK1", "", "PH_EINT12"}, + "PH13": {"LCD1_D13", "", "PS2_SDA1", "SMC_RST", "PH_EINT13"}, + "PH14": {"LCD1_D14", "ETXD3", "KP_IN4", "SMC_VPPEN", "PH_EINT14"}, + "PH15": {"LCD1_D15", "ETXD2", "KP_IN5", "SMC_VPPPP", "PH_EINT15"}, + "PH16": {"LCD1_D16", "ETXD1", "KP_IN6", "SMC_DET", "PH_EINT16"}, + "PH17": {"LCD1_D17", "ETXD0", "KP_IN7", "SMC_VCCEN", "PH_EINT17"}, + "PH18": {"LCD1_D18", "ERXCK", "KP_OUT0", "SMC_SLK", "PH_EINT18"}, + "PH19": {"LCD1_D19", "ERXERR", "KP_OUT1", "SMC_SDA", "PH_EINT19"}, + "PH20": {"LCD1_D20", "ERXDV", "CAN_TX", "", "PH_EINT20"}, + "PH21": {"LCD1_D21", "EMDC", "CAN_RX", "", "PH_EINT21"}, + "PH22": {"LCD1_D22", "EMDIO", "KP_OUT2", "SDC1_CMD", ""}, + "PH23": {"LCD1_D23", "ETXEN", "KP_OUT3", "SDC1_CLK", ""}, + "PH24": {"LCD1_CLK", "ETXCK", "KP_OUT4", "SDC1_D0", ""}, + "PH25": {"LCD1_DE", "ECRS", "KP_OUT5", "SDC1_D1", ""}, + "PH26": {"LCD1_HSYNC", "ECOL", "KP_OUT6", "SDC1_D2", ""}, + "PH27": {"LCD1_VSYNC", "ETXERR", "KP_OUT7", "SDC1_D3", ""}, + "PI0": {"", "I2C3_SCL"}, + "PI1": {"", "I2C3_SDA"}, + "PI2": {"", "I2C4_SCL"}, + "PI3": {"PWM1", "I2C4_SDA"}, + "PI4": {"SDC3_CMD"}, + "PI5": {"SDC3_CLK"}, + "PI6": {"SDC3_D0"}, + "PI7": {"SDC3_D1"}, + "PI8": {"SDC3_D2"}, + "PI9": {"SDC3_D3"}, + "PI10": {"SPI0_CS0", "UART5_TX", "", "PI_EINT22"}, + "PI11": {"SPI0_CLK", "UART5_RX", "", "PI_EINT23"}, + "PI12": {"SPI0_MOSI", "UART6_TX", "CLK_OUT_A", "PI_EINT24"}, + "PI13": {"SPI0_MISO", "UART6_RX", "CLK_OUT_B", "PI_EINT25"}, + "PI14": {"SPI0_CS0", "PS2_SCK1", "TCLKIN0", "PI_EINT26"}, + "PI15": {"SPI1_CS1", "PS2_SDA1", "TCLKIN1", "PI_EINT27"}, + "PI16": {"SPI1_CS0", "UART2_RTS", "", "PI_EINT28"}, + "PI17": {"SPI1_CLK", "UART2_CTS", "", "PI_EINT29"}, + "PI18": {"SPI1_MOSI", "UART2_TX", "", "PI_EINT30"}, + "PI19": {"SPI1_MISO", "UART2_RX", "", "PI_EINT31"}, + "PI20": {"PS2_SCK0", "UART7_TX", "HSCL"}, + "PI21": {"PS2_SDA0", "UART7_RX", "HSDA"}, +} + +// mapA20Pins uses mappingA20 to actually set the altFunc fields of all gpio +// and mark them as available. +// +// It is called by the generic allwinner processor code if an A20 is detected. +func mapA20Pins() error { + for name, altFuncs := range mappingA20 { + pin := cpupins[name] + pin.altFunc = altFuncs + pin.available = true + if strings.Contains(string(altFuncs[4]), "_EINT") || + strings.Contains(string(altFuncs[3]), "_EINT") { + pin.supportEdge = true + } + + // Initializes the sysfs corresponding pin right away. + pin.sysfsPin = sysfs.Pins[pin.Number()] + } + return nil +} diff --git a/vendor/periph.io/x/periph/host/allwinner/a64.go b/vendor/periph.io/x/periph/host/allwinner/a64.go new file mode 100644 index 0000000..82a97f8 --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/a64.go @@ -0,0 +1,174 @@ +// Copyright 2016 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. + +// This file contains pin mapping information that is specific to the Allwinner +// A64 model. + +package allwinner + +import ( + "strings" + + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/host/sysfs" +) + +// A64 specific pins. +var ( + X32KFOUT *pin.BasicPin // Clock output of 32Khz crystal + KEY_ADC *pin.BasicPin // 6 bits resolution ADC for key application; can work up to 250Hz conversion rate; reference voltage is 2.0V + EAROUTP *pin.BasicPin // Earpiece amplifier negative differential output + EAROUTN *pin.BasicPin // Earpiece amplifier positive differential output +) + +// + +func init() { + X32KFOUT = &pin.BasicPin{N: "X32KFOUT"} + // BUG(maruel): These need to be converted to an analog.PinIO implementation + // once analog support is implemented. + KEY_ADC = &pin.BasicPin{N: "KEY_ADC"} + EAROUTP = &pin.BasicPin{N: "EAROUTP"} + EAROUTN = &pin.BasicPin{N: "EAROUTN"} +} + +// mappingA64 describes the mapping of the A64 processor gpios to their +// alternate functions. +// +// It omits the in & out functions which are available on all gpio. +// +// The mapping comes from the datasheet page 23: +// http://files.pine64.org/doc/datasheet/pine64/A64_Datasheet_V1.1.pdf +// +// - The datasheet uses TWI instead of I2C but it is renamed here for +// consistency. +// - AIF is an audio interface, i.e. to connect to S/PDIF. +// - RGMII means Reduced gigabit media-independent interface. +// - SDC means SDCard? +// - NAND connects to a NAND flash controller. +// - CSI and CCI are for video capture. +var mappingA64 = map[string][5]pin.Func{ + "PB0": {"UART2_TX", "", "JTAG0_TMS", "", "PB_EINT0"}, + "PB1": {"UART2_RX", "", "JTAG0_TCK", "SIM_PWREN", "PB_EINT1"}, + "PB2": {"UART2_RTS", "", "JTAG0_TDO", "SIM_VPPEN", "PB_EINT2"}, + "PB3": {"UART2_CTS", "I2S0_MCLK", "JTAG0_TDI", "SIM_VPPPP", "PB_EINT3"}, + "PB4": {"AIF2_SYNC", "I2S0_WS", "", "SIM_CLK", "PB_EINT4"}, + "PB5": {"AIF2_BCLK", "I2S0_SCK", "", "SIM_DATA", "PB_EINT5"}, + "PB6": {"AIF2_DOUT", "I2S0_DOUT", "", "SIM_RST", "PB_EINT6"}, + "PB7": {"AIF2_DIN", "I2S0_DIN", "", "SIM_DET", "PB_EINT7"}, + "PB8": {"", "", "UART0_TX", "", "PB_EINT8"}, + "PB9": {"", "", "UART0_RX", "", "PB_EINT9"}, + "PC0": {"NAND_WE", "", "SPI0_MOSI"}, + "PC1": {"NAND_ALE", "SDC2_DS", "SPI0_MISO"}, + "PC2": {"NAND_CLE", "", "SPI0_CLK"}, + "PC3": {"NAND_CE1", "", "SPI0_CS0"}, + "PC4": {"NAND_CE0"}, + "PC5": {"NAND_RE", "SDC2_CLK"}, + "PC6": {"NAND_RB0", "SDC2_CMD"}, + "PC7": {"NAND_RB1"}, + "PC8": {"NAND_DQ0", "SDC2_D0"}, + "PC9": {"NAND_DQ1", "SDC2_D1"}, + "PC10": {"NAND_DQ2", "SDC2_D2"}, + "PC11": {"NAND_DQ3", "SDC2_D3"}, + "PC12": {"NAND_DQ4", "SDC2_D4"}, + "PC13": {"NAND_DQ5", "SDC2_D5"}, + "PC14": {"NAND_DQ6", "SDC2_D6"}, + "PC15": {"NAND_DQ7", "SDC2_D7"}, + "PC16": {"NAND_DQS", "SDC2_RST"}, + "PD0": {"LCD_D2", "UART3_TX", "SPI1_CS0", "CCIR_CLK"}, + "PD1": {"LCD_D3", "UART3_RX", "SPI1_CLK", "CCIR_DE"}, + "PD2": {"LCD_D4", "UART4_TX", "SPI1_MOSI", "CCIR_HSYNC"}, + "PD3": {"LCD_D5", "UART4_RX", "SPI1_MISO", "CCIR_VSYNC"}, + "PD4": {"LCD_D6", "UART4_RTS", "", "CCIR_D0"}, + "PD5": {"LCD_D7", "UART4_CTS", "", "CCIR_D1"}, + "PD6": {"LCD_D10", "", "", "CCIR_D2"}, + "PD7": {"LCD_D11", "", "", "CCIR_D3"}, + "PD8": {"LCD_D12", "", "RGMII_RXD3", "CCIR_D4"}, + "PD9": {"LCD_D13", "", "RGMII_RXD2", "CCIR_D5"}, + "PD10": {"LCD_D14", "", "RGMII_RXD1"}, + "PD11": {"LCD_D15", "", "RGMII_RXD0"}, + "PD12": {"LCD_D18", "LVDS_VP0", "RGMII_RXCK"}, + "PD13": {"LCD_D19", "LVDS_VN0", "RGMII_RXCT"}, + "PD14": {"LCD_D20", "LVDS_VP1", "RGMII_RXER"}, + "PD15": {"LCD_D21", "LVDS_VN1", "RGMII_TXD3", "CCIR_D6"}, + "PD16": {"LCD_D22", "LVDS_VP2", "RGMII_TXD2", "CCIR_D7"}, + "PD17": {"LCD_D23", "LVDS_VN2", "RGMII_TXD1"}, + "PD18": {"LCD_CLK", "LVDS_VPC", "RGMII_TXD0"}, + "PD19": {"LCD_DE", "LVDS_VNC", "RGMII_TXCK"}, + "PD20": {"LCD_HSYNC", "LVDS_VP3", "RGMII_TXCT"}, + "PD21": {"LCD_VSYNC", "LVDS_VN3", "RGMII_CLKI"}, + "PD22": {"PWM0", "", "MDC"}, + "PD23": {"", "", "MDIO"}, + "PD24": {""}, + "PE0": {"CSI_PCLK", "", "TS_CLK"}, + "PE1": {"CSI_MCLK", "", "TS_ERR"}, + "PE2": {"CSI_HSYNC", "", "TS_SYNC"}, + "PE3": {"CSI_VSYNC", "", "TS_DVLD"}, + "PE4": {"CSI_D0", "", "TS_D0"}, + "PE5": {"CSI_D1", "", "TS_D1"}, + "PE6": {"CSI_D2", "", "TS_D2"}, + "PE7": {"CSI_D3", "", "TS_D3"}, + "PE8": {"CSI_D4", "", "TS_D4"}, + "PE9": {"CSI_D5", "", "TS_D5"}, + "PE10": {"CSI_D6", "", "TS_D6"}, + "PE11": {"CSI_D7", "", "TS_D7"}, + "PE12": {"CSI_SCK"}, + "PE13": {"CSI_SDA"}, + "PE14": {"PLL_LOCK_DBG", "I2C2_SCL"}, + "PE15": {"", "I2C2_SDA"}, + "PE16": {""}, + "PE17": {""}, + "PF0": {"SDC0_D1", "JTAG1_TMS"}, + "PF1": {"SDC0_D0", "JTAG1_TDI"}, + "PF2": {"SDC0_CLK", "UART0_TX"}, + "PF3": {"SDC0_CMD", "JTAG1_TDO"}, + "PF4": {"SDC0_D3", "UART0_RX"}, + "PF5": {"SDC0_D2", "JTAG1_TCK"}, + "PF6": {""}, + "PG0": {"SDC1_CLK", "", "", "", "PG_EINT0"}, + "PG1": {"SDC1_CMD", "", "", "", "PG_EINT1"}, + "PG2": {"SDC1_D0", "", "", "", "PG_EINT2"}, + "PG3": {"SDC1_D1", "", "", "", "PG_EINT3"}, + "PG4": {"SDC1_D2", "", "", "", "PG_EINT4"}, + "PG5": {"SDC1_D3", "", "", "", "PG_EINT5"}, + "PG6": {"UART1_TX", "", "", "", "PG_EINT6"}, + "PG7": {"UART1_RX", "", "", "", "PG_EINT7"}, + "PG8": {"UART1_RTS", "", "", "", "PG_EINT8"}, + "PG9": {"UART1_CTS", "", "", "", "PG_EINT9"}, + "PG10": {"AIF3_SYNC", "I2S1_WS", "", "", "PG_EINT10"}, + "PG11": {"AIF3_BCLK", "I2S1_SCK", "", "", "PG_EINT11"}, + "PG12": {"AIF3_DOUT", "I2S1_DOUT", "", "", "PG_EINT12"}, + "PG13": {"AIF3_DIN", "I2S1_DIN", "", "", "PG_EINT13"}, + "PH0": {"I2C0_SCL", "", "", "", "PH_EINT0"}, + "PH1": {"I2C0_SDA", "", "", "", "PH_EINT1"}, + "PH2": {"I2C1_SCL", "", "", "", "PH_EINT2"}, + "PH3": {"I2C1_SDA", "", "", "", "PH_EINT3"}, + "PH4": {"UART3_TX", "", "", "", "PH_EINT4"}, + "PH5": {"UART3_RX", "", "", "", "PH_EINT5"}, + "PH6": {"UART3_RTS", "", "", "", "PH_EINT6"}, + "PH7": {"UART3_CTS", "", "", "", "PH_EINT7"}, + "PH8": {"OWA_OUT", "", "", "", "PH_EINT8"}, + "PH9": {"", "", "", "", "PH_EINT9"}, + "PH10": {"MIC_CLK", "", "", "", "PH_EINT10"}, + "PH11": {"MIC_DATA", "", "", "", "PH_EINT11"}, +} + +// mapA64Pins uses mappingA64 to actually set the altFunc fields of all gpio +// and mark them as available. +// +// It is called by the generic allwinner processor code if an A64 is detected. +func mapA64Pins() error { + for name, altFuncs := range mappingA64 { + pin := cpupins[name] + pin.altFunc = altFuncs + pin.available = true + if strings.Contains(string(altFuncs[4]), "_EINT") { + pin.supportEdge = true + } + + // Initializes the sysfs corresponding pin right away. + pin.sysfsPin = sysfs.Pins[pin.Number()] + } + return nil +} diff --git a/vendor/periph.io/x/periph/host/allwinner/allwinner_arm.go b/vendor/periph.io/x/periph/host/allwinner/allwinner_arm.go new file mode 100644 index 0000000..b901423 --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/allwinner_arm.go @@ -0,0 +1,7 @@ +// Copyright 2016 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 allwinner + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/allwinner/allwinner_arm64.go b/vendor/periph.io/x/periph/host/allwinner/allwinner_arm64.go new file mode 100644 index 0000000..1fb5b5f --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/allwinner_arm64.go @@ -0,0 +1,9 @@ +// Copyright 2016 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. + +// +build arm64 + +package allwinner + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/allwinner/allwinner_other.go b/vendor/periph.io/x/periph/host/allwinner/allwinner_other.go new file mode 100644 index 0000000..4795c11 --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/allwinner_other.go @@ -0,0 +1,9 @@ +// Copyright 2016 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. + +// +build !arm,!arm64 + +package allwinner + +const isArm = false diff --git a/vendor/periph.io/x/periph/host/allwinner/clock.go b/vendor/periph.io/x/periph/host/allwinner/clock.go new file mode 100644 index 0000000..f615fde --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/clock.go @@ -0,0 +1,281 @@ +// Copyright 2016 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 allwinner + +const ( + clockSPIEnable clockSPI = 1 << 31 // SCLK_GATING + // 30:26 reserved + clockSPIOSC24M clockSPI = 0 << 24 // CLK_SRC_SEL + clockSPIPLL6 clockSPI = 1 << 24 // A64: PLL_PERIPH0(1X) + clockSPIPLL5 clockSPI = 2 << 24 // A64: PLL_PERIPH1(1X) R8: PLL5 = DDR + // 23:18 reserved + clockSPIDiv1a clockSPI = 0 << 16 // CLK_DIV_RATIO_N + clockSPIDiv2a clockSPI = 1 << 16 // + clockSPIDiv4a clockSPI = 2 << 16 // + clockSPIDiv8a clockSPI = 3 << 16 // + // 15:4 reserved + clockSPIDiv1b clockSPI = 0 << 0 // CLK_DIV_RATIO_M + clockSPIDiv2b clockSPI = 1 << 0 // + clockSPIDiv3b clockSPI = 2 << 0 // + clockSPIDiv4b clockSPI = 3 << 0 // + clockSPIDiv5b clockSPI = 4 << 0 // + clockSPIDiv6b clockSPI = 5 << 0 // + clockSPIDiv7b clockSPI = 6 << 0 // + clockSPIDiv8b clockSPI = 7 << 0 // + clockSPIDiv9b clockSPI = 8 << 0 // + clockSPIDiv10b clockSPI = 9 << 0 // + clockSPIDiv11b clockSPI = 10 << 0 // + clockSPIDiv12b clockSPI = 11 << 0 // + clockSPIDiv13b clockSPI = 12 << 0 // + clockSPIDiv14b clockSPI = 13 << 0 // + clockSPIDiv15b clockSPI = 14 << 0 // + clockSPIDiv16b clockSPI = 15 << 0 // +) + +// Also valid for IR. +// +// SPI0_SCLK_CFG_REG / SPI1_SCLK_CFG_REG / SPI2_SCLK_CFG_REG / IR_SCLK_CFG_REG +// +// A64: Page 110-111. (Also Page 554?) +// R8: Page 71. +type clockSPI uint32 + +const ( + clockPLL6Enable clockPLL6R8Ctl = 1 << 31 // PLL6_Enable + clockPLL6Force24Mhz clockPLL6R8Ctl = 1 << 30 // PLL6_BYPASS_EN; force 24Mhz + // 29:13 reserved + clockPLL6FactorMulN0 clockPLL6R8Ctl = 0 << 8 // PLL6_FACTOR_N + clockPLL6FactorMulN1 clockPLL6R8Ctl = 1 << 8 // + clockPLL6FactorMulN2 clockPLL6R8Ctl = 2 << 8 // + clockPLL6FactorMulN3 clockPLL6R8Ctl = 3 << 8 // + clockPLL6FactorMulN4 clockPLL6R8Ctl = 4 << 8 // + clockPLL6FactorMulN5 clockPLL6R8Ctl = 5 << 8 // + clockPLL6FactorMulN6 clockPLL6R8Ctl = 6 << 8 // + clockPLL6FactorMulN7 clockPLL6R8Ctl = 7 << 8 // + clockPLL6FactorMulN8 clockPLL6R8Ctl = 8 << 8 // + clockPLL6FactorMulN9 clockPLL6R8Ctl = 9 << 8 // + clockPLL6FactorMulN10 clockPLL6R8Ctl = 10 << 8 // + clockPLL6FactorMulN11 clockPLL6R8Ctl = 11 << 8 // + clockPLL6FactorMulN12 clockPLL6R8Ctl = 12 << 8 // + clockPLL6FactorMulN13 clockPLL6R8Ctl = 13 << 8 // + clockPLL6FactorMulN14 clockPLL6R8Ctl = 14 << 8 // + clockPLL6FactorMulN15 clockPLL6R8Ctl = 15 << 8 // + clockPLL6FactorMulN16 clockPLL6R8Ctl = 16 << 8 // + clockPLL6FactorMulN17 clockPLL6R8Ctl = 17 << 8 // + clockPLL6FactorMulN18 clockPLL6R8Ctl = 18 << 8 // + clockPLL6FactorMulN19 clockPLL6R8Ctl = 19 << 8 // + clockPLL6FactorMulN20 clockPLL6R8Ctl = 20 << 8 // + clockPLL6FactorMulN21 clockPLL6R8Ctl = 21 << 8 // + clockPLL6FactorMulN22 clockPLL6R8Ctl = 22 << 8 // + clockPLL6FactorMulN23 clockPLL6R8Ctl = 23 << 8 // + clockPLL6FactorMulN24 clockPLL6R8Ctl = 24 << 8 // + clockPLL6FactorMulN25 clockPLL6R8Ctl = 25 << 8 // + clockPLL6FactorMulN26 clockPLL6R8Ctl = 26 << 8 // + clockPLL6FactorMulN27 clockPLL6R8Ctl = 27 << 8 // + clockPLL6FactorMulN28 clockPLL6R8Ctl = 28 << 8 // + clockPLL6FactorMulN29 clockPLL6R8Ctl = 29 << 8 // + clockPLL6FactorMulN30 clockPLL6R8Ctl = 30 << 8 // + clockPLL6FactorMulN31 clockPLL6R8Ctl = 31 << 8 // + clockPLL6Damping clockPLL6R8Ctl = 2 << 6 // + clockPLL6FactorMulK1 clockPLL6R8Ctl = 0 << 4 // PLL6_FACTOR_K + clockPLL6FactorMulK2 clockPLL6R8Ctl = 1 << 4 // + clockPLL6FactorMulK3 clockPLL6R8Ctl = 2 << 4 // + clockPLL6FactorMulK4 clockPLL6R8Ctl = 3 << 4 // + // 3:2 reserved + clockPLL6FactorDivM1 clockPLL6R8Ctl = 0 << 4 // PLL6_FACTOR_M + clockPLL6FactorDivM2 clockPLL6R8Ctl = 1 << 4 // + clockPLL6FactorDivM3 clockPLL6R8Ctl = 2 << 4 // + clockPLL6FactorDivM4 clockPLL6R8Ctl = 3 << 4 // +) + +// PLL6_CFG_REG +// R8: Page 63; default 0x21009931 +// +// Output = (24MHz*N*K)/M/2 +// Note: the output 24MHz*N*K clock must be in the range of 240MHz~3GHz if the +// bypass is disabled. +type clockPLL6R8Ctl uint32 + +// clockMap is the mapping of important registers across CPUs. +type clockMap struct { + reserved0 [0xA0 / 4]uint32 // + spi0Clk clockSPI // 0x0A0 SPI0_SCLK_CFG_REG SPI0 Clock + spi1Clk clockSPI // 0x0A4 SPI1_SCLK_CFG_REG SPI1 Clock + spi2Clk clockSPI // 0x0A8 SPI2_SCLK_CFG_REG SPI2 Clock (Not on A64) +} + +// R8: Page 57-59. +type clockMapR8 struct { + r0 uint32 // 0x000 PLL1_CFG_REG PLL1 Control + r1 uint32 // 0x004 PLL1_TUN_REG PLL1 Tuning + r2 uint32 // 0x008 PLL2_CFG_REG PLL2 Control + r3 uint32 // 0x00C PLL2_TUN_REG PLL2 Tuning + r4 uint32 // 0x010 PLL3_CFG_REG PLL3 Control + r5 uint32 // 0x014 + r6 uint32 // 0x018 PLL4_CFG_REG PLL4 Control + r7 uint32 // 0x01C + r8 uint32 // 0x020 PLL5_CFG_REG PLL5 Control + r9 uint32 // 0x024 PLL5_TUN_REG PLL5 Tuning + r10 clockPLL6R8Ctl // 0x028 PLL6_CFG_REG PLL6 Control + r11 uint32 // 0x02C PLL6 Tuning + r12 uint32 // 0x030 PLL7_CFG_REG + r13 uint32 // 0x034 + r14 uint32 // 0x038 PLL1_TUN2_REG PLL1 Tuning2 + r15 uint32 // 0x03C PLL5_TUN2_REG PLL5 Tuning2 + r16 uint32 // 0x04C + r17 uint32 // 0x050 OSC24M_CFG_REG OSC24M control + r18 uint32 // 0x054 CPU_AHB_APB0_CFG_REG CPU, AHB And APB0 Divide Ratio + r19 uint32 // 0x058 APB1_CLK_DIV_REG APB1 Clock Divider + r20 uint32 // 0x05C AXI_GATING_REG AXI Module Clock Gating + r21 uint32 // 0x060 AHB_GATING_REG0 AHB Module Clock Gating 0 + r22 uint32 // 0x064 AHB_GATING_REG1 AHB Module Clock Gating 1 + r23 uint32 // 0x068 APB0_GATING_REG APB0 Module Clock Gating + r24 uint32 // 0x06C APB1_GATING_REG APB1 Module Clock Gating + r25 uint32 // 0x080 NAND_SCLK_CFG_REG Nand Flash Clock + r26 uint32 // 0x084 + r27 uint32 // 0x088 SD0_SCLK_CFG_REG SD0 Clock + r28 uint32 // 0x08C SD1_SCLK_CFG_REG SD1 Clock + r29 uint32 // 0x090 SD2_SCLK_CFG_REG SD2 Clock + r30 uint32 // 0x094 + r31 uint32 // 0x098 + r32 uint32 // 0x09C CE_SCLK_CFG_REG Crypto Engine Clock + spi0Clk clockSPI // 0x0A0 SPI0_SCLK_CFG_REG SPI0 Clock + spi1Clk clockSPI // 0x0A4 SPI1_SCLK_CFG_REG SPI1 Clock + spi2Clk clockSPI // 0x0A8 SPI2_SCLK_CFG_REG SPI2 Clock + r33 uint32 // 0x0AC + irClk clockSPI // 0x0B0 IR_SCLK_CFG_REG IR Clock + r34 uint32 // 0x0B4 + r35 uint32 // 0x0B8 + r36 uint32 // 0x0BC + r37 uint32 // 0x0C0 + r38 uint32 // 0x0C4 + r39 uint32 // 0x0C8 + r40 uint32 // 0x0CC + r41 uint32 // 0x0D0 + r42 uint32 // 0x0D4 + r43 uint32 // 0x100 DRAM_SCLK_CFG_REG DRAM Clock + r44 uint32 // 0x104 BE_CFG_REG Display Engine Backend Clock + r45 uint32 // 0x108 + r46 uint32 // 0x10C FE_CFG_REG Display Engine Front End Clock + r47 uint32 // 0x110 + r48 uint32 // 0x114 + r49 uint32 // 0x118 + r50 uint32 // 0x11C + r51 uint32 // 0x120 + r52 uint32 // 0x124 + r53 uint32 // 0x128 + r54 uint32 // 0x12C LCD_CH1_CFG_REG LCD Channel1 Clock + r55 uint32 // 0x130 + r56 uint32 // 0x134 CSI_CFG_REG CSI Clock + r57 uint32 // 0x138 + r58 uint32 // 0x13C VE_CFG_REG Video Engine Clock + r59 uint32 // 0x140 AUDIO_CODEC_SCLK_CFG_REG Audio Codec Gating Special Clock + r60 uint32 // 0x144 AVS_SCLK_CFG_REG AVS Gating Special Clock + r61 uint32 // 0x148 + r62 uint32 // 0x14C + r63 uint32 // 0x150 + r64 uint32 // 0x154 MALI_CLOCK_CFG_REG Mali400 Gating Special Clock + r65 uint32 // 0x158 + r66 uint32 // 0x15C MBUS_SCLK_CFG_REG MBUS Gating Clock + r67 uint32 // 0x160 IEP_SCLK_CFG_REG IEP Gating Clock +} + +// A64: Page 81-84. +type clockMapA64 struct { + r0 uint32 // 0x000 PLL_CPUX_CTRL_REG PLL_CPUX Control Register + r1 uint32 // 0x008 PLL_AUDIO_CTRL_REG PLL_AUDIO Control Register + r2 uint32 // 0x010 PLL_VIDEO0_CTRL_REG PLL_VIDEO0 Control Register + r3 uint32 // 0x018 PLL_VE_CTRL_REG PLL_VE Control Register + r4 uint32 // 0x020 PLL_DDR0_CTRL_REG PLL_DDR0 Control Register + r5 uint32 // 0x028 PLL_PERIPH0_CTRL_REG PLL_PERIPH0 Control Register + r6 uint32 // 0x02C PLL_PERIPH1_CTRL_REG PLL_PERIPH1 Control Register + r7 uint32 // 0x030 PLL_VIDEO1_CTRL_REG PLL_VIDEO1 Control Register + r8 uint32 // 0x038 PLL_GPU_CTRL_REG PLL_GPU Control Register + r9 uint32 // 0x040 PLL_MIPI_CTRL_REG PLL_MIPI Control Register + r10 uint32 // 0x044 PLL_HSIC_CTRL_REG PLL_HSIC Control Register + r11 uint32 // 0x048 PLL_DE_CTRL_REG PLL_DE Control Register + r12 uint32 // 0x04C PLL_DDR1_CTRL_REG PLL_DDR1 Control Register + r13 uint32 // 0x050 CPU_AXI_CFG_REG CPUX/AXI Configuration Register + r14 uint32 // 0x054 AHB1_APB1_CFG_REG AHB1/APB1 Configuration Register + r15 uint32 // 0x058 APB2_CFG_REG APB2 Configuration Register + r16 uint32 // 0x05C AHB2_CFG_REG AHB2 Configuration Register + r17 uint32 // 0x060 BUS_CLK_GATING_REG0 Bus Clock Gating Register 0 + r18 uint32 // 0x064 BUS_CLK_GATING_REG1 Bus Clock Gating Register 1 + r19 uint32 // 0x068 BUS_CLK_GATING_REG2 Bus Clock Gating Register 2 + r20 uint32 // 0x06C BUS_CLK_GATING_REG3 Bus Clock Gating Register 3 + r21 uint32 // 0x070 BUS_CLK_GATING_REG4 Bus Clock Gating Register 4 + r22 uint32 // 0x074 THS_CLK_REG THS Clock Register + r23 uint32 // 0x080 NAND_CLK_REG NAND Clock Register + r24 uint32 // 0x088 SDMMC0_CLK_REG SDMMC0 Clock Register + r25 uint32 // 0x08C SDMMC1_CLK_REG SDMMC1 Clock Register + r26 uint32 // 0x090 SDMMC2_CLK_REG SDMMC2 Clock Register + r27 uint32 // 0x098 TS_CLK_REG TS Clock Register + r28 uint32 // 0x09C CE_CLK_REG CE Clock Register + spi0Clk clockSPI // 0x0A0 SPI0_CLK_REG SPI0 Clock Register + spi1Clk clockSPI // 0x0A4 SPI1_CLK_REG SPI1 Clock Register + r29 uint32 // 0x0B0 I2S/PCM-0_CLK_REG I2S/PCM-0 Clock Register + r30 uint32 // 0x0B4 I2S/PCM-1_CLK_REG I2S/PCM-1 Clock Register + r31 uint32 // 0x0B8 I2S/PCM-2_CLK_REG I2S/PCM-2 Clock Register + r32 uint32 // 0x0C0 SPDIF_CLK_REG SPDIF Clock Register + r33 uint32 // 0x0CC USBPHY_CFG_REG USBPHY Configuration Register + r34 uint32 // 0x0F4 DRAM_CFG_REG DRAM Configuration Register + r35 uint32 // 0x0F8 PLL_DDR_CFG_REG PLL_DDR Configuration Register + r36 uint32 // 0x0FC MBUS_RST_REG MBUS Reset Register + r37 uint32 // 0x100 DRAM_CLK_GATING_REG DRAM Clock Gating Register + r38 uint32 // 0x104 DE_CLK_REG DE Clock Register + r39 uint32 // 0x118 TCON0_CLK_REG TCON0 Clock Register + r40 uint32 // 0x11C TCON1_CLK_REG TCON1 Clock Register + r41 uint32 // 0x124 DEINTERLACE_CLK_REG DEINTERLACE Clock Register + r42 uint32 // 0x130 CSI_MISC_CLK_REG CSI_MISC Clock Register + r43 uint32 // 0x134 CSI_CLK_REG CSI Clock Register + r44 uint32 // 0x13C VE_CLK_REG VE Clock Register + r45 uint32 // 0x140 AC_DIG_CLK_REG AC Digital Clock Register + r46 uint32 // 0x144 AVS_CLK_REG AVS Clock Register + r47 uint32 // 0x150 HDMI_CLK_REG HDMI Clock Register + r48 uint32 // 0x154 HDMI_SLOW_CLK_REG HDMI Slow Clock Register + r49 uint32 // 0x15C MBUS_CLK_REG MBUS Clock Register + r50 uint32 // 0x168 MIPI_DSI_CLK_REG MIPI_DSI Clock Register + r51 uint32 // 0x1A0 GPU_CLK_REG GPU Clock Register + r52 uint32 // 0x200 PLL_STABLE_TIME_REG0 PLL Stable Time Register0 + r53 uint32 // 0x204 PLL_STABLE_TIME_REG1 PLL Stable Time Register1 + r54 uint32 // 0x21C PLL_PERIPH1_BIAS_REG PLL_PERIPH1 Bias Register + r55 uint32 // 0x220 PLL_CPUX_BIAS_REG PLL_CPUX Bias Register + r56 uint32 // 0x224 PLL_AUDIO_BIAS_REG PLL_AUDIO Bias Register + r57 uint32 // 0x228 PLL_VIDEO0_BIAS_REG PLL_VIDEO0 Bias Register + r58 uint32 // 0x22C PLL_VE_BIAS_REG PLL_VE Bias Register + r59 uint32 // 0x230 PLL_DDR0_BIAS_REG PLL_DDR0 Bias Register + r60 uint32 // 0x234 PLL_PERIPH0_BIAS_REG PLL_PERIPH0 Bias Register + r61 uint32 // 0x238 PLL_VIDEO1_BIAS_REG PLL_VIDEO1 Bias Register + r62 uint32 // 0x23C PLL_GPU_BIAS_REG PLL_GPU Bias Register + r63 uint32 // 0x240 PLL_MIPI_BIAS_REG PLL_MIPI Bias Register + r64 uint32 // 0x244 PLL_HSIC_BIAS_REG PLL_HSIC Bias Register + r65 uint32 // 0x248 PLL_DE_BIAS_REG PLL_DE Bias Register + r66 uint32 // 0x24C PLL_DDR1_BIAS_REG PLL_DDR1 Bias Register + r67 uint32 // 0x250 PLL_CPUX_TUN_REG PLL_CPUX Tuning Register + r68 uint32 // 0x260 PLL_DDR0_TUN_REG PLL_DDR0 Tuning Register + r69 uint32 // 0x270 PLL_MIPI_TUN_REG PLL_MIPI Tuning Register + r70 uint32 // 0x27C PLL_PERIPH1_PAT_CTRL_REG PLL_PERIPH1 Pattern Control Register + r71 uint32 // 0x280 PLL_CPUX_PAT_CTRL_REG PLL_CPUX Pattern Control Register + r72 uint32 // 0x284 PLL_AUDIO_PAT_CTRL_REG PLL_AUDIO Pattern Control Register + r73 uint32 // 0x288 PLL_VIDEO0_PAT_CTRL_REG PLL_VIDEO0 Pattern Control Register + r74 uint32 // 0x28C PLL_VE_PAT_CTRL_REG PLL_VE Pattern Control Register + r75 uint32 // 0x290 PLL_DDR0_PAT_CTRL_REG PLL_DDR0 Pattern Control Register + r76 uint32 // 0x298 PLL_VIDEO1_PAT_CTRL_REG PLL_VIDEO1 Pattern Control Register + r77 uint32 // 0x29C PLL_GPU_PAT_CTRL_REG PLL_GPU Pattern Control Register + r78 uint32 // 0x2A0 PLL_MIPI_PAT_CTRL_REG PLL_MIPI Pattern Control Register + r79 uint32 // 0x2A4 PLL_HSIC_PAT_CTRL_REG PLL_HSIC Pattern Control Register + r80 uint32 // 0x2A8 PLL_DE_PAT_CTRL_REG PLL_DE Pattern Control Register + r81 uint32 // 0x2AC PLL_DDR1_PAT_CTRL_REG0 PLL_DDR1 Pattern Control Register0 + r82 uint32 // 0x2B0 PLL_DDR1_PAT_CTRL_REG1 PLL_DDR1 Pattern Control Register1 + r83 uint32 // 0x2C0 BUS_SOFT_RST_REG0 Bus Software Reset Register 0 + r84 uint32 // 0x2C4 BUS_SOFT_RST_REG1 Bus Software Reset Register 1 + r85 uint32 // 0x2C8 BUS_SOFT_RST_REG2 Bus Software Reset Register 2 + r86 uint32 // 0x2D0 BUS_SOFT_RST_REG3 Bus Software Reset Register 3 + r87 uint32 // 0x2D8 BUS_SOFT_RST_REG4 Bus Software Reset Register 4 + r88 uint32 // 0x2F0 CCM_SEC_SWITCH_REG CCM Security Switch Register + r89 uint32 // 0x300 PS_CTRL_REG PS Control Register + r90 uint32 // 0x304 PS_CNT_REG PS Counter Register + r91 uint32 // 0x320 PLL_LOCK_CTRL_REG PLL Lock Control Register +} diff --git a/vendor/periph.io/x/periph/host/allwinner/detect.go b/vendor/periph.io/x/periph/host/allwinner/detect.go new file mode 100644 index 0000000..6235ee0 --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/detect.go @@ -0,0 +1,98 @@ +// Copyright 2016 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 allwinner + +import ( + "strings" + "sync" + + "periph.io/x/periph/host/distro" +) + +// Present detects whether the host CPU is an Allwinner CPU. +// +// https://en.wikipedia.org/wiki/Allwinner_Technology +func Present() bool { + detection.do() + return detection.isAllwinner +} + +// IsR8 detects whether the host CPU is an Allwinner R8 CPU. +// +// It looks for the string "sun5i-r8" in /proc/device-tree/compatible. +func IsR8() bool { + detection.do() + return detection.isR8 +} + +// IsA20 detects whether the host CPU is an Allwinner A20 CPU. +// +// It first looks for the string "sun71-a20" in /proc/device-tree/compatible, +// and if that fails it checks for "Hardware : sun7i" in /proc/cpuinfo. +func IsA20() bool { + detection.do() + return detection.isA20 +} + +// IsA64 detects whether the host CPU is an Allwinner A64 CPU. +// +// It looks for the string "sun50iw1p1" in /proc/device-tree/compatible. +func IsA64() bool { + detection.do() + return detection.isA64 +} + +// + +type detectionS struct { + mu sync.Mutex + done bool + isAllwinner bool + isR8 bool + isA20 bool + isA64 bool +} + +var detection detectionS + +// do contains the CPU detection logic that determines whether we have an +// Allwinner CPU and if so, which exact model. +// +// Sadly there is no science behind this, it's more of a trial and error using +// as many boards and OS flavors as possible. +func (d *detectionS) do() { + d.mu.Lock() + defer d.mu.Unlock() + if !d.done { + d.done = true + if isArm { + for _, c := range distro.DTCompatible() { + if strings.Contains(c, "sun50iw1p1") { + d.isA64 = true + } + if strings.Contains(c, "sun5i-r8") { + d.isR8 = true + } + if strings.Contains(c, "sun7i-a20") { + d.isA20 = true + } + } + d.isAllwinner = d.isA64 || d.isR8 || d.isA20 + + if !d.isAllwinner { + // The kernel in the image that comes pre-installed on the pcDuino3 Nano + // is an old 3.x kernel that doesn't expose the device-tree in procfs, + // so do an extra check in cpuinfo as well if we haven't detected + // anything yet. + // Distros based on 4.x kernels do expose it. + if hw, ok := distro.CPUInfo()["Hardware"]; ok { + if hw == "sun7i" { + d.isA20 = true + } + } + } + } + } +} diff --git a/vendor/periph.io/x/periph/host/allwinner/dma.go b/vendor/periph.io/x/periph/host/allwinner/dma.go new file mode 100644 index 0000000..6a608b8 --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/dma.go @@ -0,0 +1,474 @@ +// Copyright 2016 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. + +// Unlike the bcm283x, the allwinner CPUs do not have a "clear bit" and "set +// bit" registers, they only have the data register. Also, allwinner CPUs do +// not support linked lists of DMA buffers. On the other hand, the Allwinner DMA +// controller supports 8 bits transfers instead of 32-128 bits that the bcm283x +// DMA controller supports. +// +// This means that only 8 bits can be used per sample, and only one stream is +// necessary. This results in 1/8th th memory usage than on the bcm283x. The +// drawback is that a block of 8 contiguous GPIO pins must be dedicated to the +// stream. + +package allwinner + +import ( + "errors" + "fmt" + "log" + "os" + + "periph.io/x/periph" + "periph.io/x/periph/host/pmem" +) + +// dmaMap represents the DMA memory mapped CPU registers. +// +// This map is specific to the currently supported CPUs and will have to be +// adapted as more CPUs are supported. In particular the number of physical +// channels varies across different CPUs. +// +// Note that we modify the DMA controllers without telling the kernel driver. +// The driver keeps its own table of which DMA channel is available so this +// code could effectively crash the whole system. It practice this works. +// #everythingisfine +type dmaMap struct { + irqEn dmaR8Irq // DMA_IRQ_EN_REG + irqPendStas dmaR8PendingIrq // DMA_IRQ_PEND_STAS_REG + reserved0 [(0x100 - 8) / 4]uint32 // + normal [8]dmaR8NormalGroup // 0x100 The "8" "normal" DMA channels (only one active at a time so there's effectively one) + reserved1 [0x100 / 4]uint32 // + dedicated [8]dmaDedicatedGroup // 0x300 The 8 "dedicated" (as in actually existing) DMA channels +} + +func (d *dmaMap) getDedicated() int { + for i := len(d.dedicated) - 1; i >= 0; i-- { + if d.dedicated[i].isAvailable() { + return i + } + } + return -1 +} + +// dmaNormalGroup is the control registers for the first block of 8 DMA +// controllers. +// +// They can be intentionally slowed down, unlike the dedicated DMA ones. +// +// The big caveat is that only one controller can be active at a time and the +// execution sequence is in accordance with the priority level. This means that +// two normal DMA cannot be used to do simultaneous read and write. This +// feature is critical for bus bitbanging. +type dmaR8NormalGroup struct { + cfg ndmaR8Cfg // NDMA_CTRL_REG + srcAddr uint32 // NDMA_SRC_ADDR_REG + dstAddr uint32 // NDMA_DEST_ADDR_REG + byteCounter uint32 // NDMA_BC_REG + reserved [4]uint32 // +} + +func (d *dmaR8NormalGroup) isAvailable() bool { + return d.cfg == 0 && d.srcAddr == 0 && d.dstAddr == 0 && d.byteCounter == 0 +} + +func (d *dmaR8NormalGroup) release() error { + d.srcAddr = 0 + d.dstAddr = 0 + d.byteCounter = 0 + d.cfg = ndmaLoad + //drvDMA.dmaMemory.irqEn &^= ... + //drvDMA.dmaMemory.irqPendStas &^= ... + return nil +} + +// dmaNormalGroup is the control registers for the second block of 8 DMA +// controllers. +// +// They support different DReq and can do non-linear streaming. +type dmaDedicatedGroup struct { + cfg ddmaR8Cfg // DDMA_CTRL_REG + srcAddr uint32 // DDMA_SRC_ADDR_REG + dstAddr uint32 // DDMA_DEST_ADDR_REG + byteCounter uint32 // DDMA_BC_REG (24 bits) + reserved0 [2]uint32 // + param ddmaR8Param // DDMA_PARA_REG (dedicated DMA only) + reserved1 uint32 // +} + +func (d *dmaDedicatedGroup) isAvailable() bool { + return d.cfg == 0 && d.srcAddr == 0 && d.dstAddr == 0 && d.byteCounter == 0 && d.param == 0 +} + +func (d *dmaDedicatedGroup) set(srcAddr, dstAddr, l uint32, srcIO, dstIO bool, src ddmaR8Cfg) { + d.srcAddr = srcAddr + d.dstAddr = dstAddr + d.byteCounter = l + // TODO(maruel): Slow down the clock by another 2*250x + //d.param = ddmaR8Param(250 | 250<<16) + d.param = ddmaR8Param(1<<24 | 1<<8 | 1) + // All these have value 0. This statement only exist for documentation. + cfg := ddmaDstWidth8 | ddmaDstBurst1 | ddmaDstLinear | ddmaSrcWidth8 | ddmaSrcLinear | ddmaSrcBurst1 + cfg |= src | ddmaBCRemain + if srcIO { + cfg |= ddmaSrcIOMode + } else if dstIO { + cfg |= ddmaDstIOMode + } + d.cfg = ddmaLoad | cfg + for i := 0; d.cfg&ddmaLoad != 0 && i < 100000; i++ { + } + if d.cfg&ddmaLoad != 0 { + log.Printf("failed to load DDMA: %# v\n", d) + } +} + +func (d *dmaDedicatedGroup) release() error { + d.param = 0 + d.srcAddr = 0 + d.dstAddr = 0 + d.byteCounter = 0 + d.cfg = ddmaLoad + //drvDMA.dmaMemory.irqEn &^= ... + //drvDMA.dmaMemory.irqPendStas &^= ... + return nil +} + +const ( + // 31 reserved + dma7QueueEndIrq dmaA64Irq = 1 << 30 // DMA7_END_IRQ_EN; DMA 7 Queue End Transfer Interrupt Enable. + dma7PackageEndIrq dmaA64Irq = 1 << 29 // DMA7_PKG_IRQ_EN; DMA 7 Package End Transfer Interrupt Enable. + dma7HalfIrq dmaA64Irq = 1 << 28 // DMA7_HLAF_IRQ_EN; DMA 7 Half Package Transfer Interrupt Enable. + // ... + // 3 reserved + dma0QueueEndIrq dmaA64Irq = 1 << 2 // DMA0_END_IRQ_EN; DMA 0 Queue End Transfer Interrupt Enable. + dma0PackageEndIrq dmaA64Irq = 1 << 1 // DMA0_PKG_IRQ_EN; DMA 0 Package End Transfer Interrupt Enable. + dma0HalfIrq dmaA64Irq = 1 << 0 // DMA0_HLAF_IRQ_EN; DMA 0 Half Package Transfer Interrupt Enable. +) + +// DMA_IRQ_EN_REG +// A64: Page 199-201. +type dmaA64Irq uint32 + +const ( + ddma7EndIrq dmaR8Irq = 1 << 31 // DDMA7_END_IRQ_EN + ddma7HalfIreq dmaR8Irq = 1 << 30 // DDMA7_HF_IRQ_EN + // ... + ddma0EndIrq dmaR8Irq = 1 << 17 // DDMA0_END_IRQ_EN + ddma0HalfIreq dmaR8Irq = 1 << 16 // DDMA0_HF_IRQ_EN + ndma7EndIrq dmaR8Irq = 1 << 15 // NDMA7_END_IRQ_EN + ndma7HalfIreq dmaR8Irq = 1 << 16 // NDDMA7_HF_IRQ_EN + // ... + ndma0EndIrq dmaR8Irq = 1 << 1 // NDMA0_END_IRQ_EN + ndma0HFIreq dmaR8Irq = 1 << 0 // NDMA0_HF_IRQ_EN +) + +// DMA_IRQ_EN_REG +// R8: Page 124-126. +type dmaR8Irq uint32 + +const ( + // 31 reserved + dma7QueueEndIrqPend dmaA64PendingIrq = 1 << 30 // DMA7_QUEUE_IRQ_PEND; DMA 7 Queue End Transfer Interrupt Pending. Set 1 to the bit will clear it. + dma7PackageEndIrqPend dmaA64PendingIrq = 1 << 29 // DMA7_PKG_IRQ_PEND; DMA 7 Package End Transfer Interrupt Pending. Set 1 to the bit will clear it. + dma7HalfIrqPend dmaA64PendingIrq = 1 << 28 // DMA7_HLAF_IRQ_PEND; DMA 7 Half Package Transfer Interrupt Pending. Set 1 to the bit will clear it. + // ... + // 3 reserved + dma0QueueEndIrqPend dmaA64PendingIrq = 1 << 2 // DMA0_QUEUE_IRQ_PEND; DMA 0 Queue End Transfer Interrupt Pending. Set 1 to the bit will clear it. + dma0PackageEndIrqPend dmaA64PendingIrq = 1 << 1 // DMA0_PKG_IRQ_PEND; DMA 0 Package End Transfer Interrupt Pending. Set 1 to the bit will clear it. + dma0HalfIrqPend dmaA64PendingIrq = 1 << 0 // DMA0_HLAF_IRQ_PEND; DMA 0 Half Package Transfer Interrupt Pending. Set 1 to the bit will clear it. +) + +// DMA_IRQ_PEND_REG0 +// A64: Page 201-203. +type dmaA64PendingIrq uint32 + +const ( + ddma7EndIrqPend dmaR8PendingIrq = 1 << 31 // DDMA7_END_IRQ_PEND + ddma7HalfIreqPend dmaR8PendingIrq = 1 << 30 // DDMA7_HF_IRQ_PEND + // ... + ddma0EndIrqPend dmaR8PendingIrq = 1 << 17 // DDMA0_END_IRQ_PEND + ddma0HalfIreqPend dmaR8PendingIrq = 1 << 16 // DDMA0_HF_IRQ_PEND + ndma7EndIrqPend dmaR8PendingIrq = 1 << 15 // NDMA7_END_IRQ_PEND + ndma7HalfIreqPend dmaR8PendingIrq = 1 << 16 // NDDMA7_HF_IRQ_PEND + // ... + ndma0EndIrqPend dmaR8PendingIrq = 1 << 1 // NDMA0_END_IRQ_PEND + ndma0HalfIreqPend dmaR8PendingIrq = 1 << 0 // NDMA0_HF_IRQ_PEND +) + +// DMA_IRQ_PEND_STAS_REG +// R8: Page 126-129. +type dmaR8PendingIrq uint32 + +const ( + ndmaLoad ndmaR8Cfg = 1 << 31 // NDMA_LOAD + ndmaContinuous ndmaR8Cfg = 1 << 30 // NDMA_CONTI_EN Continuous mode + ndmaWaitClk0 ndmaR8Cfg = 0 << 27 // NDMA_WAIT_STATE Number of clock to wait for + ndmaWaitClk2 ndmaR8Cfg = 1 << 27 // 2(n+1) + ndmaWaitClk6 ndmaR8Cfg = 2 << 27 // + ndmaWaitClk8 ndmaR8Cfg = 3 << 27 // + ndmaWaitClk10 ndmaR8Cfg = 4 << 27 // + ndmaWaitClk12 ndmaR8Cfg = 5 << 27 // + ndmaWaitClk14 ndmaR8Cfg = 6 << 27 // + ndmaWaitClk16 ndmaR8Cfg = 7 << 27 // + ndmaDstWidth32 ndmaR8Cfg = 2 << 25 // NDMA_DST_DATA_WIDTH + ndmaDstWidth16 ndmaR8Cfg = 1 << 25 // + ndmaDstWidth8 ndmaR8Cfg = 0 << 25 // + ndmaDstBurst8 ndmaR8Cfg = 2 << 23 // NDMA_DST_BST_LEN + ndmaDstBurst4 ndmaR8Cfg = 1 << 23 // + ndmaDstBurst1 ndmaR8Cfg = 0 << 23 // + // 22 reserved NDMA_CFG_DST_NON_SECURE ? + ndmaDstAddrNoInc ndmaR8Cfg = 1 << 21 // NDMA_DST_ADDR_TYPE + ndmaDstDrqIRTX ndmaR8Cfg = 0 << 16 // NDMA_DST_DRQ_TYPE + ndmaDstDrqUART1TX ndmaR8Cfg = 9 << 16 // + ndmaDstDrqUART3TX ndmaR8Cfg = 11 << 16 // + ndmaDstDrqAudio ndmaR8Cfg = 19 << 16 // 24.576MHz (Page 53) + ndmaDstDrqSRAM ndmaR8Cfg = 21 << 16 // + ndmaDstDrqSPI0TX ndmaR8Cfg = 24 << 16 // + ndmaDstDrqSPI1TX ndmaR8Cfg = 25 << 16 // + ndmaDstDrqSPI2TX ndmaR8Cfg = 26 << 16 // + ndmaDstDrqUSB1 ndmaR8Cfg = 27 << 16 // 480MHz + ndmaDstDrqUSB2 ndmaR8Cfg = 28 << 16 // + ndmaDstDrqUSB3 ndmaR8Cfg = 29 << 16 // + ndmaDstDrqUSB4 ndmaR8Cfg = 30 << 16 // + ndmaDstDrqUSB5 ndmaR8Cfg = 31 << 16 // + ndmaBCRemain ndmaR8Cfg = 1 << 15 // BC_MODE_SEL + // 14:11 reserved + ndmaSrcWidth32 ndmaR8Cfg = 2 << 9 // NDMA_SRC_DATA_WIDTH + ndmaSrcWidth16 ndmaR8Cfg = 1 << 9 // + ndmaSrcWidth8 ndmaR8Cfg = 0 << 9 // + ndmaSrcBurst8 ndmaR8Cfg = 2 << 7 // NDMA_SRC_BST_LEN + ndmaSrcBurst4 ndmaR8Cfg = 1 << 7 // + ndmaSrcBurst1 ndmaR8Cfg = 0 << 7 // + // 6 reserved NDMA_CFG_SRC_NON_SECURE ? + ndmaSrcAddrNoInc ndmaR8Cfg = 1 << 5 // NDMA_SRC_ADDR_TYPE + ndmaSrcDrqIRTX ndmaR8Cfg = 0 << 0 // NDMA_SRC_DRQ_TYPE + ndmaSrcDrqUART1RX ndmaR8Cfg = 9 << 0 // + ndmaSrcDrqUART3RX ndmaR8Cfg = 11 << 0 // + ndmaSrcDrqAudio ndmaR8Cfg = 19 << 0 // 24.576MHz (Page 53) + ndmaSrcDrqSRAM ndmaR8Cfg = 21 << 0 // + ndmaSrcDrqSDRAM ndmaR8Cfg = 22 << 0 // 0~400MHz + ndmaSrcDrqTPAD ndmaR8Cfg = 23 << 0 // + ndmaSrcDrqSPI0RX ndmaR8Cfg = 24 << 0 // + ndmaSrcDrqSPI1RX ndmaR8Cfg = 25 << 0 // + ndmaSrcDrqSPI2RX ndmaR8Cfg = 26 << 0 // + ndmaSrcDrqUSB1 ndmaR8Cfg = 27 << 0 // 480MHz + ndmaSrcDrqUSB2 ndmaR8Cfg = 28 << 0 // + ndmaSrcDrqUSB3 ndmaR8Cfg = 29 << 0 // + ndmaSrcDrqUSB4 ndmaR8Cfg = 30 << 0 // + ndmaSrcDrqUSB5 ndmaR8Cfg = 31 << 0 // +) + +// NDMA_CTRL_REG +// R8: Page 129-131. +type ndmaR8Cfg uint32 + +const ( + ddmaLoad ddmaR8Cfg = 1 << 31 // DDMA_LOAD + ddmaBusy ddmaR8Cfg = 1 << 30 // DDMA_BSY_STA + ddmaContinuous ddmaR8Cfg = 1 << 29 // DDMA_CONTI_MODE_EN + // 28:27 reserved 28 = DDMA_CFG_DST_NON_SECURE ? + ddmaDstWidth32 ddmaR8Cfg = 2 << 25 // DDMA_DST_DATA_WIDTH + ddmaDstWidth16 ddmaR8Cfg = 1 << 25 // + ddmaDstWidth8 ddmaR8Cfg = 0 << 25 // + ddmaDstBurst8 ddmaR8Cfg = 2 << 23 // DDMA_DST_BST_LEN + ddmaDstBurst4 ddmaR8Cfg = 1 << 23 // + ddmaDstBurst1 ddmaR8Cfg = 0 << 23 // + ddmaDstVertical ddmaR8Cfg = 3 << 21 // DDMA_ADDR_MODE; no idea what it's use it. It's not explained in the datasheet ... + ddmaDstHorizontal ddmaR8Cfg = 2 << 21 // ... and the official drivers/dma/sun6i-dma.c driver doesn't use it + ddmaDstIOMode ddmaR8Cfg = 1 << 21 // Non incrementing + ddmaDstLinear ddmaR8Cfg = 0 << 21 // Normal incrementing position + ddmaDstDrqSRAM ddmaR8Cfg = 0 << 16 // DDMA_DST_DRQ_SEL + ddmaDstDrqSDRAM ddmaR8Cfg = 1 << 16 // DDR ram speed + ddmaDstDrqNAND ddmaR8Cfg = 3 << 16 // + ddmaDstDrqUSB0 ddmaR8Cfg = 4 << 16 // + ddmaDstDrqSPI1TX ddmaR8Cfg = 8 << 16 // + ddmaDstDrqCryptoTX ddmaR8Cfg = 10 << 16 // + ddmaDstDrqTCON0 ddmaR8Cfg = 14 << 16 // + ddmaDstDrqSPI0TX ddmaR8Cfg = 26 << 16 // + ddmaDstDrqSPI2TX ddmaR8Cfg = 28 << 16 // + ddmaBCRemain ddmaR8Cfg = 1 << 15 // BC_MODE_SEL + // 14:11 reserved + ddmaSrcWidth32 ddmaR8Cfg = 2 << 9 // DDMA_SRC_DATA_WIDTH + ddmaSrcWidth16 ddmaR8Cfg = 1 << 9 // + ddmaSrcWidth8 ddmaR8Cfg = 0 << 9 // + ddmaSrcBurst8 ddmaR8Cfg = 2 << 7 // DDMA_SRC_BST_LEN + ddmaSrcBurst4 ddmaR8Cfg = 1 << 7 // + ddmaSrcBurst1 ddmaR8Cfg = 0 << 7 // + ddmaSrcVertical ddmaR8Cfg = 3 << 5 // DDMA_SRC_ADDR_MODE + ddmaSrcHorizontal ddmaR8Cfg = 2 << 5 // + ddmaSrcIOMode ddmaR8Cfg = 1 << 5 // Non incrementing + ddmaSrcLinear ddmaR8Cfg = 0 << 5 // Normal incrementing position + // 4:0 drq + ddmaSrcDrqSRAM ddmaR8Cfg = 0 << 0 // DDMA_SRC_DRQ_TYPE + ddmaSrcDrqSDRAM ddmaR8Cfg = 1 << 0 // + ddmaSrcDrqNAND ddmaR8Cfg = 3 << 0 // + ddmaSrcDrqUSB0 ddmaR8Cfg = 4 << 0 // + ddmaSrcDrqSPI1RX ddmaR8Cfg = 9 << 0 // + ddmaSrcDrqCryptoRX ddmaR8Cfg = 11 << 0 // + ddmaSrcDrqSPI0RX ddmaR8Cfg = 27 << 0 // + ddmaSrcDrqSPI2RX ddmaR8Cfg = 29 << 0 // +) + +// DDMA_CFG_REG +// R8: Page 131-134. +type ddmaR8Cfg uint32 + +const ( + // For each value, N+1 is actually used. + ddmaDstBlkSizeMask ddmaR8Param = 0xFF << 24 // DEST_DATA_BLK_SIZE + ddmaDstWaitClkCycleMask ddmaR8Param = 0xFF << 16 // DEST_WAIT_CLK_CYC + ddmaSrcBlkSizeMask ddmaR8Param = 0xFF << 8 // SRC_DATA_BLK_SIZE + ddmaSrcWaitClkCycleMask ddmaR8Param = 0xFF << 0 // SRC_WAIT_CLK_CYC +) + +// DDMA_PARA_REG +// R8: Page 134. +type ddmaR8Param uint32 + +// smokeTest allocates two physical pages, ask the DMA controller to copy the +// data from one page to another (with a small offset) and make sure the +// content is as expected. +// +// This should take a fraction of a second and will make sure the driver is +// usable. +func smokeTest() error { + const size = 4096 // 4kb + const holeSize = 1 // Minimum DMA alignment. + + alloc := func(s int) (pmem.Mem, error) { + return pmem.Alloc(s) + } + + copyMem := func(pDst, pSrc uint64) error { + n := drvDMA.dmaMemory.getDedicated() + if n == -1 { + return errors.New("no channel available") + } + drvDMA.dmaMemory.irqEn &^= 3 << uint(2*n+16) + drvDMA.dmaMemory.irqPendStas = 3 << uint(2*n+16) + ch := &drvDMA.dmaMemory.dedicated[n] + defer func() { + _ = ch.release() + }() + ch.set(uint32(pSrc), uint32(pDst)+holeSize, 4096-2*holeSize, false, false, ddmaDstDrqSDRAM|ddmaSrcDrqSDRAM) + + for ch.cfg&ddmaBusy != 0 { + } + return nil + } + + return pmem.TestCopy(size, holeSize, alloc, copyMem) +} + +// driverDMA implements periph.Driver. +// +// It implements much more than the DMA controller, it also exposes the clocks, +// the PWM and PCM controllers. +type driverDMA struct { + // dmaMemory is the memory map of the CPU DMA registers. + dmaMemory *dmaMap + // pwmMemory is the memory map of the CPU PWM registers. + pwmMemory *pwmMap + // spiMemory is the memory mapping for the spi CPU registers. + spiMemory *spiMap + // clockMemory is the memory mapping for the clock CPU registers. + clockMemory *clockMap + // timerMemory is the memory mapping for the timer CPU registers. + timerMemory *timerMap +} + +func (d *driverDMA) String() string { + return "allwinner-dma" +} + +func (d *driverDMA) Prerequisites() []string { + return []string{"allwinner-gpio"} +} + +func (d *driverDMA) After() []string { + return nil +} + +func (d *driverDMA) Init() (bool, error) { + // dmaBaseAddr is the physical base address of the DMA registers. + var dmaBaseAddr uint32 + // pwmBaseAddr is the physical base address of the PWM registers. + var pwmBaseAddr uint32 + // spiBaseAddr is the physical base address of the clock registers. + var spiBaseAddr uint32 + // clockBaseAddr is the physical base address of the clock registers. + var clockBaseAddr uint32 + // timerBaseAddr is the physical base address of the timer registers. + var timerBaseAddr uint32 + if IsA64() { + // Page 198. + dmaBaseAddr = 0x1C02000 + // Page 194. + pwmBaseAddr = 0x1C21400 + // Page 161. + timerBaseAddr = 0x1C20C00 + // Page 81. + clockBaseAddr = 0x1C20000 + // Page Page 545. + spiBaseAddr = 0x01C68000 + } else if IsR8() { + // Page 124. + dmaBaseAddr = 0x1C02000 + // Page 83. + pwmBaseAddr = 0x1C20C00 + 0x200 + // Page 85. + timerBaseAddr = 0x1C20C00 + // Page 57. + clockBaseAddr = 0x1C20000 + // Page 151. + spiBaseAddr = 0x01C05000 + } else { + // H3 + // Page 194. + //dmaBaseAddr = 0x1C02000 + // Page 187. + //pwmBaseAddr = 0x1C21400 + // Page 154. + //timerBaseAddr = 0x1C20C00 + return false, errors.New("unsupported CPU architecture") + } + + if err := pmem.MapAsPOD(uint64(dmaBaseAddr), &d.dmaMemory); err != nil { + if os.IsPermission(err) { + return true, fmt.Errorf("need more access, try as root: %v", err) + } + return true, err + } + + if err := pmem.MapAsPOD(uint64(pwmBaseAddr), &d.pwmMemory); err != nil { + return true, err + } + if err := pmem.MapAsPOD(uint64(timerBaseAddr), &d.timerMemory); err != nil { + return true, err + } + if err := pmem.MapAsPOD(uint64(clockBaseAddr), &d.clockMemory); err != nil { + return true, err + } + if err := pmem.MapAsPOD(uint64(spiBaseAddr), &d.spiMemory); err != nil { + return true, err + } + + return true, smokeTest() +} + +func (d *driverDMA) Close() error { + // Stop DMA and PWM controllers. + return nil +} + +func init() { + if false && isArm { + // TODO(maruel): This is intense, wait to be sure it works. + periph.MustRegister(&drvDMA) + } +} + +var drvDMA driverDMA diff --git a/vendor/periph.io/x/periph/host/allwinner/doc.go b/vendor/periph.io/x/periph/host/allwinner/doc.go new file mode 100644 index 0000000..c2cc83c --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/doc.go @@ -0,0 +1,33 @@ +// Copyright 2016 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 allwinner exposes the GPIO functionality that is common to all +// AllWinner processors. +// +// This driver implements memory-mapped GPIO pin manipulation and leverages +// sysfs-gpio for edge detection. +// +// If you are looking at the actual implementation, open doc.go for further +// implementation details. +// +// Datasheets +// +// A64: http://files.pine64.org/doc/datasheet/pine64/Allwinner_A64_User_Manual_V1.0.pdf +// +// H3: http://dl.linux-sunxi.org/H3/Allwinner_H3_Datasheet_V1.0.pdf +// +// R8: https://github.com/NextThingCo/CHIP-Hardware/raw/master/CHIP%5Bv1_0%5D/CHIPv1_0-BOM-Datasheets/Allwinner%20R8%20User%20Manual%20V1.1.pdf +// +// Physical overview: http://files.pine64.org/doc/datasheet/pine64/A64_Datasheet_V1.1.pdf +package allwinner + +// Other implementation details +// +// The most active kernel branch is +// https://github.com/linux-sunxi/linux-sunxi/commits/sunxi-next +// +// In particular look at +// https://github.com/linux-sunxi/linux-sunxi/blob/sunxi-next/drivers/dma/sun4i-dma.c +// and +// https://github.com/linux-sunxi/linux-sunxi/blob/sunxi-next/drivers/dma/sun6i-dma.c diff --git a/vendor/periph.io/x/periph/host/allwinner/gpio.go b/vendor/periph.io/x/periph/host/allwinner/gpio.go new file mode 100644 index 0000000..3054468 --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/gpio.go @@ -0,0 +1,1065 @@ +// Copyright 2016 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. + +// This file contains the definitions of all possible generic Allwinner pins and their +// implementation using a combination of sysfs and memory-mapped I/O. + +package allwinner + +import ( + "errors" + "fmt" + "os" + "path" + "strconv" + "strings" + "time" + + "periph.io/x/periph" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/gpio/gpioreg" + "periph.io/x/periph/conn/physic" + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/host/pmem" + "periph.io/x/periph/host/sysfs" +) + +// List of all known pins. These global variables can be used directly. +// +// The supported functionality of each gpio differs between CPUs. For example +// the R8 has the LCD-DE signal on gpio PD25 but the A64 has it on PD19. +// +// The availability of each gpio differs between CPUs. For example the R8 has +// 19 pins in the group PB but the A64 only has 10. +// +// So make sure to read the datasheet for the exact right CPU. +var ( + PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PA10, PA11, PA12, PA13, PA14, PA15, PA16, PA17 *Pin + PB0, PB1, PB2, PB3, PB4, PB5, PB6, PB7, PB8, PB9, PB10, PB11, PB12, PB13, PB14, PB15, PB16, PB17, PB18, PB19, PB20, PB21, PB22, PB23 *Pin + PC0, PC1, PC2, PC3, PC4, PC5, PC6, PC7, PC8, PC9, PC10, PC11, PC12, PC13, PC14, PC15, PC16, PC17, PC18, PC19, PC20, PC21, PC22, PC23, PC24 *Pin + PD0, PD1, PD2, PD3, PD4, PD5, PD6, PD7, PD8, PD9, PD10, PD11, PD12, PD13, PD14, PD15, PD16, PD17, PD18, PD19, PD20, PD21, PD22, PD23, PD24, PD25, PD26, PD27 *Pin + PE0, PE1, PE2, PE3, PE4, PE5, PE6, PE7, PE8, PE9, PE10, PE11, PE12, PE13, PE14, PE15, PE16, PE17 *Pin + PF0, PF1, PF2, PF3, PF4, PF5, PF6 *Pin + PG0, PG1, PG2, PG3, PG4, PG5, PG6, PG7, PG8, PG9, PG10, PG11, PG12, PG13 *Pin + PH0, PH1, PH2, PH3, PH4, PH5, PH6, PH7, PH8, PH9, PH10, PH11, PH12, PH13, PH14, PH15, PH16, PH17, PH18, PH19, PH20, PH21, PH22, PH23, PH24, PH25, PH26, PH27 *Pin + PI0, PI1, PI2, PI3, PI4, PI5, PI6, PI7, PI8, PI9, PI10, PI11, PI12, PI13, PI14, PI15, PI16, PI17, PI18, PI19, PI20, PI21 *Pin +) + +// Pin implements the gpio.PinIO interface for generic Allwinner CPU pins using +// memory mapping for gpio in/out functionality. +type Pin struct { + // Immutable. + group uint8 // as per register offset calculation + offset uint8 // as per register offset calculation + name string // name as per datasheet + defaultPull gpio.Pull // default pull at startup + + // Immutable after driver initialization. + altFunc [5]pin.Func // alternate functions + sysfsPin *sysfs.Pin // Set to the corresponding sysfs.Pin, if any. + available bool // Set when the pin is available on this CPU architecture. + supportEdge bool // Set when the pin supports interrupt based edge detection. + + // Mutable. + usingEdge bool // Set when edge detection is enabled. +} + +// String implements conn.Resource. +// +// It returns the pin name and number, ex: "PB5(37)". +func (p *Pin) String() string { + return fmt.Sprintf("%s(%d)", p.name, p.Number()) +} + +// Halt implements conn.Resource. +// +// It stops edge detection if enabled. +func (p *Pin) Halt() error { + if p.usingEdge { + if err := p.sysfsPin.Halt(); err != nil { + return p.wrap(err) + } + p.usingEdge = false + } + return nil +} + +// Name implements pin.Pin. +// +// It returns the pin name, ex: "PB5". +func (p *Pin) Name() string { + return p.name +} + +// Number implements pin.Pin. +// +// It returns the GPIO pin number as represented by gpio sysfs. +func (p *Pin) Number() int { + return int(p.group)*32 + int(p.offset) +} + +// Function implements pin.Pin. +func (p *Pin) Function() string { + return string(p.Func()) +} + +// Func implements pin.PinFunc. +func (p *Pin) Func() pin.Func { + if !p.available { + return pin.FuncNone + } + if drvGPIO.gpioMemory == nil { + if p.sysfsPin == nil { + return pin.FuncNone + } + return p.sysfsPin.Func() + } + switch f := p.function(); f { + case in: + if p.FastRead() { + return gpio.IN_HIGH + } + return gpio.IN_LOW + case out: + if p.FastRead() { + return gpio.OUT_HIGH + } + return gpio.OUT_LOW + case alt1: + if p.altFunc[0] != "" { + return pin.Func(p.altFunc[0]) + } + return pin.Func("ALT1") + case alt2: + if p.altFunc[1] != "" { + return pin.Func(p.altFunc[1]) + } + return pin.Func("ALT2") + case alt3: + if p.altFunc[2] != "" { + return pin.Func(p.altFunc[2]) + } + return pin.Func("ALT3") + case alt4: + if p.altFunc[3] != "" { + if strings.Contains(string(p.altFunc[3]), "EINT") { + // It's an input supporting interrupts. + if p.FastRead() { + return gpio.IN_HIGH + } + return gpio.IN_LOW + } + return pin.Func(p.altFunc[3]) + } + return pin.Func("ALT4") + case alt5: + if p.altFunc[4] != "" { + if strings.Contains(string(p.altFunc[4]), "EINT") { + // It's an input supporting interrupts. + if p.FastRead() { + return gpio.IN_HIGH + } + return gpio.IN_LOW + } + return pin.Func(p.altFunc[4]) + } + return pin.Func("ALT5") + case disabled: + return pin.FuncNone + default: + return pin.FuncNone + } +} + +// SupportedFuncs implements pin.PinFunc. +func (p *Pin) SupportedFuncs() []pin.Func { + f := make([]pin.Func, 0, 2+4) + f = append(f, gpio.IN, gpio.OUT) + for _, m := range p.altFunc { + if m != pin.FuncNone && !strings.Contains(string(m), "EINT") { + f = append(f, m) + } + } + return f +} + +// SetFunc implements pin.PinFunc. +func (p *Pin) SetFunc(f pin.Func) error { + switch f { + case gpio.FLOAT: + return p.In(gpio.Float, gpio.NoEdge) + case gpio.IN: + return p.In(gpio.PullNoChange, gpio.NoEdge) + case gpio.IN_LOW: + return p.In(gpio.PullDown, gpio.NoEdge) + case gpio.IN_HIGH: + return p.In(gpio.PullUp, gpio.NoEdge) + case gpio.OUT_HIGH: + return p.Out(gpio.High) + case gpio.OUT_LOW: + return p.Out(gpio.Low) + default: + isGeneral := f == f.Generalize() + for i, m := range p.altFunc { + if m == f || (isGeneral && m.Generalize() == f) { + if err := p.Halt(); err != nil { + return err + } + switch i { + case 0: + p.setFunction(alt1) + case 1: + p.setFunction(alt2) + case 2: + p.setFunction(alt3) + case 3: + p.setFunction(alt4) + case 4: + p.setFunction(alt5) + } + return nil + } + } + return p.wrap(errors.New("unsupported function")) + } +} + +// In implements gpio.PinIn. +// +// It sets the pin direction to input and optionally enables a pull-up/down +// resistor as well as edge detection. +// +// Not all pins support edge detection on Allwinner processors! +// +// Edge detection requires opening a gpio sysfs file handle. The pin will be +// exported at /sys/class/gpio/gpio*/. Note that the pin will not be unexported +// at shutdown. +func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error { + if !p.available { + // We do not want the error message about uninitialized system. + return p.wrap(errors.New("not available on this CPU architecture")) + } + if edge != gpio.NoEdge && !p.supportEdge { + return p.wrap(errors.New("edge detection is not supported on this pin")) + } + if p.usingEdge && edge == gpio.NoEdge { + if err := p.sysfsPin.Halt(); err != nil { + return p.wrap(err) + } + p.usingEdge = false + } + if drvGPIO.gpioMemory == nil { + if p.sysfsPin == nil { + return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible; try running as root?")) + } + if pull != gpio.PullNoChange { + return p.wrap(errors.New("pull cannot be used when subsystem gpiomem not initialized; try running as root?")) + } + if err := p.sysfsPin.In(pull, edge); err != nil { + return p.wrap(err) + } + p.usingEdge = edge != gpio.NoEdge + return nil + } + p.setFunction(in) + if pull != gpio.PullNoChange { + off := p.offset / 16 + shift := 2 * (p.offset % 16) + // Do it in a way that is concurrency safe. + drvGPIO.gpioMemory.groups[p.group].pull[off] &^= 3 << shift + switch pull { + case gpio.PullDown: + drvGPIO.gpioMemory.groups[p.group].pull[off] = 2 << shift + case gpio.PullUp: + drvGPIO.gpioMemory.groups[p.group].pull[off] = 1 << shift + default: + } + } + if edge != gpio.NoEdge { + if p.sysfsPin == nil { + return p.wrap(fmt.Errorf("pin %d is not exported by sysfs", p.Number())) + } + // This resets pending edges. + if err := p.sysfsPin.In(gpio.PullNoChange, edge); err != nil { + return p.wrap(err) + } + p.usingEdge = true + } + return nil +} + +// Read implements gpio.PinIn. +// +// It returns the current pin level. This function is fast. +func (p *Pin) Read() gpio.Level { + if !p.available { + return gpio.Low + } + if drvGPIO.gpioMemory == nil { + if p.sysfsPin == nil { + return gpio.Low + } + return p.sysfsPin.Read() + } + return gpio.Level(drvGPIO.gpioMemory.groups[p.group].data&(1<> (2 * (p.offset % 16))) & 3 { + case 0: + return gpio.Float + case 1: + return gpio.PullUp + case 2: + return gpio.PullDown + default: + // Confused. + return gpio.PullNoChange + } +} + +// DefaultPull implements gpio.PinIn. +func (p *Pin) DefaultPull() gpio.Pull { + return p.defaultPull +} + +// Out implements gpio.PinOut. +func (p *Pin) Out(l gpio.Level) error { + if !p.available { + // We do not want the error message about uninitialized system. + return p.wrap(errors.New("not available on this CPU architecture")) + } + if drvGPIO.gpioMemory == nil { + if p.sysfsPin != nil { + return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible; try running as root?")) + } + return p.sysfsPin.Out(l) + } + // First disable edges. + if err := p.Halt(); err != nil { + return err + } + p.FastOut(l) + p.setFunction(out) + return nil +} + +// FastOut sets a pin output level with Absolutely No error checking. +// +// Out() Must be called once first before calling FastOut(), otherwise the +// behavior is undefined. Then FastOut() can be used for minimal CPU overhead +// to reach Mhz scale bit banging. +func (p *Pin) FastOut(l gpio.Level) { + bit := uint32(1 << p.offset) + // Pn_DAT n*0x24+0x10 Port n Data Register (n from 0(A) to 8(I)) + // This is a switch on p.group rather than an index to the groups array for + // performance reasons: to avoid Go's array bound checking code. + // See https://periph.io/news/2017/gpio_perf/ for details. + switch p.group { + case 0: + if l { + drvGPIO.gpioMemory.groups[0].data |= bit + } else { + drvGPIO.gpioMemory.groups[0].data &^= bit + } + case 1: + if l { + drvGPIO.gpioMemory.groups[1].data |= bit + } else { + drvGPIO.gpioMemory.groups[1].data &^= bit + } + case 2: + if l { + drvGPIO.gpioMemory.groups[2].data |= bit + } else { + drvGPIO.gpioMemory.groups[2].data &^= bit + } + case 3: + if l { + drvGPIO.gpioMemory.groups[3].data |= bit + } else { + drvGPIO.gpioMemory.groups[3].data &^= bit + } + case 4: + if l { + drvGPIO.gpioMemory.groups[4].data |= bit + } else { + drvGPIO.gpioMemory.groups[4].data &^= bit + } + case 5: + if l { + drvGPIO.gpioMemory.groups[5].data |= bit + } else { + drvGPIO.gpioMemory.groups[5].data &^= bit + } + case 6: + if l { + drvGPIO.gpioMemory.groups[6].data |= bit + } else { + drvGPIO.gpioMemory.groups[6].data &^= bit + } + case 7: + if l { + drvGPIO.gpioMemory.groups[7].data |= bit + } else { + drvGPIO.gpioMemory.groups[7].data &^= bit + } + case 8: + if l { + drvGPIO.gpioMemory.groups[8].data |= bit + } else { + drvGPIO.gpioMemory.groups[8].data &^= bit + } + } +} + +// PWM implements gpio.PinOut. +func (p *Pin) PWM(gpio.Duty, physic.Frequency) error { + return p.wrap(errors.New("not available on this CPU architecture")) +} + +// + +// drive returns the configured output current drive strength for this GPIO. +// +// The value returned by this function is not yet verified to be correct. Use +// with suspicion. +func (p *Pin) drive() physic.ElectricCurrent { + if drvGPIO.gpioMemory == nil { + return 0 + } + // Explanation of the buffer configuration, but doesn't state what's the + // expected drive strength! + // http://files.pine64.org/doc/datasheet/pine-h64/Allwinner_H6%20V200_User_Manual_V1.1.pdf + // Section 3.21.3.4 page 381~382 + // + // The A64 and H3 datasheets call for 20mA, so it could be reasonable to + // think that the values are 5mA, 10mA, 15mA, 20mA but we don't know for + // sure. + v := (drvGPIO.gpioMemory.groups[p.group].drv[p.offset/16] >> (2 * (p.offset & 15))) & 3 + return physic.ElectricCurrent(v+1) * 5 * physic.MilliAmpere +} + +// function returns the current GPIO pin function. +func (p *Pin) function() function { + if drvGPIO.gpioMemory == nil { + return disabled + } + shift := 4 * (p.offset % 8) + return function((drvGPIO.gpioMemory.groups[p.group].cfg[p.offset/8] >> shift) & 7) +} + +// setFunction changes the GPIO pin function. +func (p *Pin) setFunction(f function) { + off := p.offset / 8 + shift := 4 * (p.offset % 8) + mask := uint32(disabled) << shift + v := (uint32(f) << shift) ^ mask + // First disable, then setup. This is concurrent safe. + drvGPIO.gpioMemory.groups[p.group].cfg[off] |= mask + drvGPIO.gpioMemory.groups[p.group].cfg[off] &^= v + if p.function() != f { + panic(f) + } +} + +func (p *Pin) wrap(err error) error { + return fmt.Errorf("allwinner-gpio (%s): %v", p, err) +} + +// + +// A64: Page 23~24 +// R8: Page 322-334. +// Each pin can have one of 7 functions. +const ( + in function = 0 + out function = 1 + alt1 function = 2 + alt2 function = 3 + alt3 function = 4 + alt4 function = 5 + alt5 function = 6 // often interrupt based edge detection as input + disabled function = 7 +) + +// cpupins that may be implemented by a generic Allwinner CPU. Not all pins +// will be present on all models and even if the CPU model supports them they +// may not be connected to anything on the board. The net effect is that it may +// look like more pins are available than really are, but trying to get the pin +// list 100% correct on all platforms seems futile, hence periph errs on the +// side of caution. +var cpupins = map[string]*Pin{ + "PA0": {group: 0, offset: 0, name: "PA0", defaultPull: gpio.Float}, + "PA1": {group: 0, offset: 1, name: "PA1", defaultPull: gpio.Float}, + "PA2": {group: 0, offset: 2, name: "PA2", defaultPull: gpio.Float}, + "PA3": {group: 0, offset: 3, name: "PA3", defaultPull: gpio.Float}, + "PA4": {group: 0, offset: 4, name: "PA4", defaultPull: gpio.Float}, + "PA5": {group: 0, offset: 5, name: "PA5", defaultPull: gpio.Float}, + "PA6": {group: 0, offset: 6, name: "PA6", defaultPull: gpio.Float}, + "PA7": {group: 0, offset: 7, name: "PA7", defaultPull: gpio.Float}, + "PA8": {group: 0, offset: 8, name: "PA8", defaultPull: gpio.Float}, + "PA9": {group: 0, offset: 9, name: "PA9", defaultPull: gpio.Float}, + "PA10": {group: 0, offset: 10, name: "PA10", defaultPull: gpio.Float}, + "PA11": {group: 0, offset: 11, name: "PA11", defaultPull: gpio.Float}, + "PA12": {group: 0, offset: 12, name: "PA12", defaultPull: gpio.Float}, + "PA13": {group: 0, offset: 13, name: "PA13", defaultPull: gpio.Float}, + "PA14": {group: 0, offset: 14, name: "PA14", defaultPull: gpio.Float}, + "PA15": {group: 0, offset: 15, name: "PA15", defaultPull: gpio.Float}, + "PA16": {group: 0, offset: 16, name: "PA16", defaultPull: gpio.Float}, + "PA17": {group: 0, offset: 17, name: "PA17", defaultPull: gpio.Float}, + "PB0": {group: 1, offset: 0, name: "PB0", defaultPull: gpio.Float}, + "PB1": {group: 1, offset: 1, name: "PB1", defaultPull: gpio.Float}, + "PB2": {group: 1, offset: 2, name: "PB2", defaultPull: gpio.Float}, + "PB3": {group: 1, offset: 3, name: "PB3", defaultPull: gpio.Float}, + "PB4": {group: 1, offset: 4, name: "PB4", defaultPull: gpio.Float}, + "PB5": {group: 1, offset: 5, name: "PB5", defaultPull: gpio.Float}, + "PB6": {group: 1, offset: 6, name: "PB6", defaultPull: gpio.Float}, + "PB7": {group: 1, offset: 7, name: "PB7", defaultPull: gpio.Float}, + "PB8": {group: 1, offset: 8, name: "PB8", defaultPull: gpio.Float}, + "PB9": {group: 1, offset: 9, name: "PB9", defaultPull: gpio.Float}, + "PB10": {group: 1, offset: 10, name: "PB10", defaultPull: gpio.Float}, + "PB11": {group: 1, offset: 11, name: "PB11", defaultPull: gpio.Float}, + "PB12": {group: 1, offset: 12, name: "PB12", defaultPull: gpio.Float}, + "PB13": {group: 1, offset: 13, name: "PB13", defaultPull: gpio.Float}, + "PB14": {group: 1, offset: 14, name: "PB14", defaultPull: gpio.Float}, + "PB15": {group: 1, offset: 15, name: "PB15", defaultPull: gpio.Float}, + "PB16": {group: 1, offset: 16, name: "PB16", defaultPull: gpio.Float}, + "PB17": {group: 1, offset: 17, name: "PB17", defaultPull: gpio.Float}, + "PB18": {group: 1, offset: 18, name: "PB18", defaultPull: gpio.Float}, + "PB19": {group: 1, offset: 19, name: "PB19", defaultPull: gpio.Float}, + "PB20": {group: 1, offset: 20, name: "PB20", defaultPull: gpio.Float}, + "PB21": {group: 1, offset: 21, name: "PB21", defaultPull: gpio.Float}, + "PB22": {group: 1, offset: 22, name: "PB22", defaultPull: gpio.Float}, + "PB23": {group: 1, offset: 23, name: "PB23", defaultPull: gpio.Float}, + "PC0": {group: 2, offset: 0, name: "PC0", defaultPull: gpio.Float}, + "PC1": {group: 2, offset: 1, name: "PC1", defaultPull: gpio.Float}, + "PC2": {group: 2, offset: 2, name: "PC2", defaultPull: gpio.Float}, + "PC3": {group: 2, offset: 3, name: "PC3", defaultPull: gpio.PullUp}, + "PC4": {group: 2, offset: 4, name: "PC4", defaultPull: gpio.PullUp}, + "PC5": {group: 2, offset: 5, name: "PC5", defaultPull: gpio.Float}, + "PC6": {group: 2, offset: 6, name: "PC6", defaultPull: gpio.PullUp}, + "PC7": {group: 2, offset: 7, name: "PC7", defaultPull: gpio.PullUp}, + "PC8": {group: 2, offset: 8, name: "PC8", defaultPull: gpio.Float}, + "PC9": {group: 2, offset: 9, name: "PC9", defaultPull: gpio.Float}, + "PC10": {group: 2, offset: 10, name: "PC10", defaultPull: gpio.Float}, + "PC11": {group: 2, offset: 11, name: "PC11", defaultPull: gpio.Float}, + "PC12": {group: 2, offset: 12, name: "PC12", defaultPull: gpio.Float}, + "PC13": {group: 2, offset: 13, name: "PC13", defaultPull: gpio.Float}, + "PC14": {group: 2, offset: 14, name: "PC14", defaultPull: gpio.Float}, + "PC15": {group: 2, offset: 15, name: "PC15", defaultPull: gpio.Float}, + "PC16": {group: 2, offset: 16, name: "PC16", defaultPull: gpio.Float}, + "PC17": {group: 2, offset: 17, name: "PC17", defaultPull: gpio.Float}, + "PC18": {group: 2, offset: 18, name: "PC18", defaultPull: gpio.Float}, + "PC19": {group: 2, offset: 19, name: "PC19", defaultPull: gpio.Float}, + "PC20": {group: 2, offset: 20, name: "PC20", defaultPull: gpio.Float}, + "PC21": {group: 2, offset: 21, name: "PC21", defaultPull: gpio.Float}, + "PC22": {group: 2, offset: 22, name: "PC22", defaultPull: gpio.Float}, + "PC23": {group: 2, offset: 23, name: "PC23", defaultPull: gpio.Float}, + "PC24": {group: 2, offset: 24, name: "PC24", defaultPull: gpio.Float}, + "PD0": {group: 3, offset: 0, name: "PD0", defaultPull: gpio.Float}, + "PD1": {group: 3, offset: 1, name: "PD1", defaultPull: gpio.Float}, + "PD2": {group: 3, offset: 2, name: "PD2", defaultPull: gpio.Float}, + "PD3": {group: 3, offset: 3, name: "PD3", defaultPull: gpio.Float}, + "PD4": {group: 3, offset: 4, name: "PD4", defaultPull: gpio.Float}, + "PD5": {group: 3, offset: 5, name: "PD5", defaultPull: gpio.Float}, + "PD6": {group: 3, offset: 6, name: "PD6", defaultPull: gpio.Float}, + "PD7": {group: 3, offset: 7, name: "PD7", defaultPull: gpio.Float}, + "PD8": {group: 3, offset: 8, name: "PD8", defaultPull: gpio.Float}, + "PD9": {group: 3, offset: 9, name: "PD9", defaultPull: gpio.Float}, + "PD10": {group: 3, offset: 10, name: "PD10", defaultPull: gpio.Float}, + "PD11": {group: 3, offset: 11, name: "PD11", defaultPull: gpio.Float}, + "PD12": {group: 3, offset: 12, name: "PD12", defaultPull: gpio.Float}, + "PD13": {group: 3, offset: 13, name: "PD13", defaultPull: gpio.Float}, + "PD14": {group: 3, offset: 14, name: "PD14", defaultPull: gpio.Float}, + "PD15": {group: 3, offset: 15, name: "PD15", defaultPull: gpio.Float}, + "PD16": {group: 3, offset: 16, name: "PD16", defaultPull: gpio.Float}, + "PD17": {group: 3, offset: 17, name: "PD17", defaultPull: gpio.Float}, + "PD18": {group: 3, offset: 18, name: "PD18", defaultPull: gpio.Float}, + "PD19": {group: 3, offset: 19, name: "PD19", defaultPull: gpio.Float}, + "PD20": {group: 3, offset: 20, name: "PD20", defaultPull: gpio.Float}, + "PD21": {group: 3, offset: 21, name: "PD21", defaultPull: gpio.Float}, + "PD22": {group: 3, offset: 22, name: "PD22", defaultPull: gpio.Float}, + "PD23": {group: 3, offset: 23, name: "PD23", defaultPull: gpio.Float}, + "PD24": {group: 3, offset: 24, name: "PD24", defaultPull: gpio.Float}, + "PD25": {group: 3, offset: 25, name: "PD25", defaultPull: gpio.Float}, + "PD26": {group: 3, offset: 26, name: "PD26", defaultPull: gpio.Float}, + "PD27": {group: 3, offset: 27, name: "PD27", defaultPull: gpio.Float}, + "PE0": {group: 4, offset: 0, name: "PE0", defaultPull: gpio.Float}, + "PE1": {group: 4, offset: 1, name: "PE1", defaultPull: gpio.Float}, + "PE2": {group: 4, offset: 2, name: "PE2", defaultPull: gpio.Float}, + "PE3": {group: 4, offset: 3, name: "PE3", defaultPull: gpio.Float}, + "PE4": {group: 4, offset: 4, name: "PE4", defaultPull: gpio.Float}, + "PE5": {group: 4, offset: 5, name: "PE5", defaultPull: gpio.Float}, + "PE6": {group: 4, offset: 6, name: "PE6", defaultPull: gpio.Float}, + "PE7": {group: 4, offset: 7, name: "PE7", defaultPull: gpio.Float}, + "PE8": {group: 4, offset: 8, name: "PE8", defaultPull: gpio.Float}, + "PE9": {group: 4, offset: 9, name: "PE9", defaultPull: gpio.Float}, + "PE10": {group: 4, offset: 10, name: "PE10", defaultPull: gpio.Float}, + "PE11": {group: 4, offset: 11, name: "PE11", defaultPull: gpio.Float}, + "PE12": {group: 4, offset: 12, name: "PE12", defaultPull: gpio.Float}, + "PE13": {group: 4, offset: 13, name: "PE13", defaultPull: gpio.Float}, + "PE14": {group: 4, offset: 14, name: "PE14", defaultPull: gpio.Float}, + "PE15": {group: 4, offset: 15, name: "PE15", defaultPull: gpio.Float}, + "PE16": {group: 4, offset: 16, name: "PE16", defaultPull: gpio.Float}, + "PE17": {group: 4, offset: 17, name: "PE17", defaultPull: gpio.Float}, + "PF0": {group: 5, offset: 0, name: "PF0", defaultPull: gpio.Float}, + "PF1": {group: 5, offset: 1, name: "PF1", defaultPull: gpio.Float}, + "PF2": {group: 5, offset: 2, name: "PF2", defaultPull: gpio.Float}, + "PF3": {group: 5, offset: 3, name: "PF3", defaultPull: gpio.Float}, + "PF4": {group: 5, offset: 4, name: "PF4", defaultPull: gpio.Float}, + "PF5": {group: 5, offset: 5, name: "PF5", defaultPull: gpio.Float}, + "PF6": {group: 5, offset: 6, name: "PF6", defaultPull: gpio.Float}, + "PG0": {group: 6, offset: 0, name: "PG0", defaultPull: gpio.Float}, + "PG1": {group: 6, offset: 1, name: "PG1", defaultPull: gpio.Float}, + "PG2": {group: 6, offset: 2, name: "PG2", defaultPull: gpio.Float}, + "PG3": {group: 6, offset: 3, name: "PG3", defaultPull: gpio.Float}, + "PG4": {group: 6, offset: 4, name: "PG4", defaultPull: gpio.Float}, + "PG5": {group: 6, offset: 5, name: "PG5", defaultPull: gpio.Float}, + "PG6": {group: 6, offset: 6, name: "PG6", defaultPull: gpio.Float}, + "PG7": {group: 6, offset: 7, name: "PG7", defaultPull: gpio.Float}, + "PG8": {group: 6, offset: 8, name: "PG8", defaultPull: gpio.Float}, + "PG9": {group: 6, offset: 9, name: "PG9", defaultPull: gpio.Float}, + "PG10": {group: 6, offset: 10, name: "PG10", defaultPull: gpio.Float}, + "PG11": {group: 6, offset: 11, name: "PG11", defaultPull: gpio.Float}, + "PG12": {group: 6, offset: 12, name: "PG12", defaultPull: gpio.Float}, + "PG13": {group: 6, offset: 13, name: "PG13", defaultPull: gpio.Float}, + "PH0": {group: 7, offset: 0, name: "PH0", defaultPull: gpio.Float}, + "PH1": {group: 7, offset: 1, name: "PH1", defaultPull: gpio.Float}, + "PH2": {group: 7, offset: 2, name: "PH2", defaultPull: gpio.Float}, + "PH3": {group: 7, offset: 3, name: "PH3", defaultPull: gpio.Float}, + "PH4": {group: 7, offset: 4, name: "PH4", defaultPull: gpio.Float}, + "PH5": {group: 7, offset: 5, name: "PH5", defaultPull: gpio.Float}, + "PH6": {group: 7, offset: 6, name: "PH6", defaultPull: gpio.Float}, + "PH7": {group: 7, offset: 7, name: "PH7", defaultPull: gpio.Float}, + "PH8": {group: 7, offset: 8, name: "PH8", defaultPull: gpio.Float}, + "PH9": {group: 7, offset: 9, name: "PH9", defaultPull: gpio.Float}, + "PH10": {group: 7, offset: 10, name: "PH10", defaultPull: gpio.Float}, + "PH11": {group: 7, offset: 11, name: "PH11", defaultPull: gpio.Float}, + "PH12": {group: 7, offset: 12, name: "PH12", defaultPull: gpio.Float}, + "PH13": {group: 7, offset: 13, name: "PH13", defaultPull: gpio.Float}, + "PH14": {group: 7, offset: 14, name: "PH14", defaultPull: gpio.Float}, + "PH15": {group: 7, offset: 15, name: "PH15", defaultPull: gpio.Float}, + "PH16": {group: 7, offset: 16, name: "PH16", defaultPull: gpio.Float}, + "PH17": {group: 7, offset: 17, name: "PH17", defaultPull: gpio.Float}, + "PH18": {group: 7, offset: 18, name: "PH18", defaultPull: gpio.Float}, + "PH19": {group: 7, offset: 19, name: "PH19", defaultPull: gpio.Float}, + "PH20": {group: 7, offset: 20, name: "PH20", defaultPull: gpio.Float}, + "PH21": {group: 7, offset: 21, name: "PH21", defaultPull: gpio.Float}, + "PH22": {group: 7, offset: 22, name: "PH22", defaultPull: gpio.Float}, + "PH23": {group: 7, offset: 23, name: "PH23", defaultPull: gpio.Float}, + "PH24": {group: 7, offset: 24, name: "PH24", defaultPull: gpio.Float}, + "PH25": {group: 7, offset: 25, name: "PH25", defaultPull: gpio.Float}, + "PH26": {group: 7, offset: 26, name: "PH26", defaultPull: gpio.Float}, + "PH27": {group: 7, offset: 27, name: "PH27", defaultPull: gpio.Float}, + "PI0": {group: 8, offset: 0, name: "PI0", defaultPull: gpio.Float}, + "PI1": {group: 8, offset: 1, name: "PI1", defaultPull: gpio.Float}, + "PI2": {group: 8, offset: 2, name: "PI2", defaultPull: gpio.Float}, + "PI3": {group: 8, offset: 3, name: "PI3", defaultPull: gpio.Float}, + "PI4": {group: 8, offset: 4, name: "PI4", defaultPull: gpio.Float}, + "PI5": {group: 8, offset: 5, name: "PI5", defaultPull: gpio.Float}, + "PI6": {group: 8, offset: 6, name: "PI6", defaultPull: gpio.Float}, + "PI7": {group: 8, offset: 7, name: "PI7", defaultPull: gpio.Float}, + "PI8": {group: 8, offset: 8, name: "PI8", defaultPull: gpio.Float}, + "PI9": {group: 8, offset: 9, name: "PI9", defaultPull: gpio.Float}, + "PI10": {group: 8, offset: 10, name: "PI10", defaultPull: gpio.Float}, + "PI11": {group: 8, offset: 11, name: "PI11", defaultPull: gpio.Float}, + "PI12": {group: 8, offset: 12, name: "PI12", defaultPull: gpio.Float}, + "PI13": {group: 8, offset: 13, name: "PI13", defaultPull: gpio.Float}, + "PI14": {group: 8, offset: 14, name: "PI14", defaultPull: gpio.Float}, + "PI15": {group: 8, offset: 15, name: "PI15", defaultPull: gpio.Float}, + "PI16": {group: 8, offset: 16, name: "PI16", defaultPull: gpio.Float}, + "PI17": {group: 8, offset: 17, name: "PI17", defaultPull: gpio.Float}, + "PI18": {group: 8, offset: 18, name: "PI18", defaultPull: gpio.Float}, + "PI19": {group: 8, offset: 19, name: "PI19", defaultPull: gpio.Float}, + "PI20": {group: 8, offset: 20, name: "PI20", defaultPull: gpio.Float}, + "PI21": {group: 8, offset: 21, name: "PI21", defaultPull: gpio.Float}, +} + +func init() { + PA0 = cpupins["PA0"] + PA1 = cpupins["PA1"] + PA2 = cpupins["PA2"] + PA3 = cpupins["PA3"] + PA4 = cpupins["PA4"] + PA5 = cpupins["PA5"] + PA6 = cpupins["PA6"] + PA7 = cpupins["PA7"] + PA8 = cpupins["PA8"] + PA9 = cpupins["PA9"] + PA10 = cpupins["PA10"] + PA11 = cpupins["PA11"] + PA12 = cpupins["PA12"] + PA13 = cpupins["PA13"] + PA14 = cpupins["PA14"] + PA15 = cpupins["PA15"] + PA16 = cpupins["PA16"] + PA17 = cpupins["PA17"] + PB0 = cpupins["PB0"] + PB1 = cpupins["PB1"] + PB2 = cpupins["PB2"] + PB3 = cpupins["PB3"] + PB4 = cpupins["PB4"] + PB5 = cpupins["PB5"] + PB6 = cpupins["PB6"] + PB7 = cpupins["PB7"] + PB8 = cpupins["PB8"] + PB9 = cpupins["PB9"] + PB10 = cpupins["PB10"] + PB11 = cpupins["PB11"] + PB12 = cpupins["PB12"] + PB13 = cpupins["PB13"] + PB14 = cpupins["PB14"] + PB15 = cpupins["PB15"] + PB16 = cpupins["PB16"] + PB17 = cpupins["PB17"] + PB18 = cpupins["PB18"] + PB19 = cpupins["PB19"] + PB20 = cpupins["PB20"] + PB21 = cpupins["PB21"] + PB22 = cpupins["PB22"] + PB23 = cpupins["PB23"] + PC0 = cpupins["PC0"] + PC1 = cpupins["PC1"] + PC2 = cpupins["PC2"] + PC3 = cpupins["PC3"] + PC4 = cpupins["PC4"] + PC5 = cpupins["PC5"] + PC6 = cpupins["PC6"] + PC7 = cpupins["PC7"] + PC8 = cpupins["PC8"] + PC9 = cpupins["PC9"] + PC10 = cpupins["PC10"] + PC11 = cpupins["PC11"] + PC12 = cpupins["PC12"] + PC13 = cpupins["PC13"] + PC14 = cpupins["PC14"] + PC15 = cpupins["PC15"] + PC16 = cpupins["PC16"] + PC17 = cpupins["PC17"] + PC18 = cpupins["PC18"] + PC19 = cpupins["PC19"] + PC20 = cpupins["PC20"] + PC21 = cpupins["PC21"] + PC22 = cpupins["PC22"] + PC23 = cpupins["PC23"] + PC24 = cpupins["PC24"] + PD0 = cpupins["PD0"] + PD1 = cpupins["PD1"] + PD2 = cpupins["PD2"] + PD3 = cpupins["PD3"] + PD4 = cpupins["PD4"] + PD5 = cpupins["PD5"] + PD6 = cpupins["PD6"] + PD7 = cpupins["PD7"] + PD8 = cpupins["PD8"] + PD9 = cpupins["PD9"] + PD10 = cpupins["PD10"] + PD11 = cpupins["PD11"] + PD12 = cpupins["PD12"] + PD13 = cpupins["PD13"] + PD14 = cpupins["PD14"] + PD15 = cpupins["PD15"] + PD16 = cpupins["PD16"] + PD17 = cpupins["PD17"] + PD18 = cpupins["PD18"] + PD19 = cpupins["PD19"] + PD20 = cpupins["PD20"] + PD21 = cpupins["PD21"] + PD22 = cpupins["PD22"] + PD23 = cpupins["PD23"] + PD24 = cpupins["PD24"] + PD25 = cpupins["PD25"] + PD26 = cpupins["PD26"] + PD27 = cpupins["PD27"] + PE0 = cpupins["PE0"] + PE1 = cpupins["PE1"] + PE2 = cpupins["PE2"] + PE3 = cpupins["PE3"] + PE4 = cpupins["PE4"] + PE5 = cpupins["PE5"] + PE6 = cpupins["PE6"] + PE7 = cpupins["PE7"] + PE8 = cpupins["PE8"] + PE9 = cpupins["PE9"] + PE10 = cpupins["PE10"] + PE11 = cpupins["PE11"] + PE12 = cpupins["PE12"] + PE13 = cpupins["PE13"] + PE14 = cpupins["PE14"] + PE15 = cpupins["PE15"] + PE16 = cpupins["PE16"] + PE17 = cpupins["PE17"] + PF0 = cpupins["PF0"] + PF1 = cpupins["PF1"] + PF2 = cpupins["PF2"] + PF3 = cpupins["PF3"] + PF4 = cpupins["PF4"] + PF5 = cpupins["PF5"] + PF6 = cpupins["PF6"] + PG0 = cpupins["PG0"] + PG1 = cpupins["PG1"] + PG2 = cpupins["PG2"] + PG3 = cpupins["PG3"] + PG4 = cpupins["PG4"] + PG5 = cpupins["PG5"] + PG6 = cpupins["PG6"] + PG7 = cpupins["PG7"] + PG8 = cpupins["PG8"] + PG9 = cpupins["PG9"] + PG10 = cpupins["PG10"] + PG11 = cpupins["PG11"] + PG12 = cpupins["PG12"] + PG13 = cpupins["PG13"] + PH0 = cpupins["PH0"] + PH1 = cpupins["PH1"] + PH2 = cpupins["PH2"] + PH3 = cpupins["PH3"] + PH4 = cpupins["PH4"] + PH5 = cpupins["PH5"] + PH6 = cpupins["PH6"] + PH7 = cpupins["PH7"] + PH8 = cpupins["PH8"] + PH9 = cpupins["PH9"] + PH10 = cpupins["PH10"] + PH11 = cpupins["PH11"] + PH12 = cpupins["PH12"] + PH13 = cpupins["PH13"] + PH14 = cpupins["PH14"] + PH15 = cpupins["PH15"] + PH16 = cpupins["PH16"] + PH17 = cpupins["PH17"] + PH18 = cpupins["PH18"] + PH19 = cpupins["PH19"] + PH20 = cpupins["PH20"] + PH21 = cpupins["PH21"] + PH22 = cpupins["PH22"] + PH23 = cpupins["PH23"] + PH24 = cpupins["PH24"] + PH25 = cpupins["PH25"] + PH26 = cpupins["PH26"] + PH27 = cpupins["PH27"] + PI0 = cpupins["PI0"] + PI1 = cpupins["PI1"] + PI2 = cpupins["PI2"] + PI3 = cpupins["PI3"] + PI4 = cpupins["PI4"] + PI5 = cpupins["PI5"] + PI6 = cpupins["PI6"] + PI7 = cpupins["PI7"] + PI8 = cpupins["PI8"] + PI9 = cpupins["PI9"] + PI10 = cpupins["PI10"] + PI11 = cpupins["PI11"] + PI12 = cpupins["PI12"] + PI13 = cpupins["PI13"] + PI14 = cpupins["PI14"] + PI15 = cpupins["PI15"] + PI16 = cpupins["PI16"] + PI17 = cpupins["PI17"] + PI18 = cpupins["PI18"] + PI19 = cpupins["PI19"] + PI20 = cpupins["PI20"] + PI21 = cpupins["PI21"] +} + +// initPins initializes the mapping of pins by function, sets the alternate +// functions of each pin, and registers all the pins with gpio. +func initPins() error { + functions := map[pin.Func]struct{}{} + for name, p := range cpupins { + num := strconv.Itoa(p.Number()) + gpion := "GPIO" + num + + // Unregister the pin if already registered. This happens with sysfs-gpio. + // Do not error on it, since sysfs-gpio may have failed to load. + _ = gpioreg.Unregister(gpion) + _ = gpioreg.Unregister(num) + + // Register the pin with gpio. + if err := gpioreg.Register(p); err != nil { + return err + } + if err := gpioreg.RegisterAlias(gpion, name); err != nil { + return err + } + if err := gpioreg.RegisterAlias(num, name); err != nil { + return err + } + switch f := p.Func(); f { + case gpio.IN, gpio.OUT, pin.FuncNone: + default: + // Registering the same alias twice fails. This can happen if two pins + // are configured with the same function. + if _, ok := functions[f]; !ok { + functions[f] = struct{}{} + if err := gpioreg.RegisterAlias(string(f), name); err != nil { + return err + } + } + } + } + + // Now do a second loop but do the alternate functions. + for name, p := range cpupins { + for _, f := range p.SupportedFuncs() { + switch f { + case gpio.IN, gpio.OUT: + default: + if _, ok := functions[f]; !ok { + functions[f] = struct{}{} + if err := gpioreg.RegisterAlias(string(f), name); err != nil { + return err + } + } + } + } + } + return nil +} + +// function encodes the active functionality of a pin. The alternate functions +// are GPIO pin dependent. +type function uint8 + +// gpioGroup is a memory-mapped structure for the hardware registers that +// control a group of at most 32 pins. In practice the number of valid pins per +// group varies between 10 and 27. +// +// http://files.pine64.org/doc/datasheet/pine64/Allwinner_A64_User_Manual_V1.0.pdf +// Page 376 GPIO PB to PH. +// Page 410 GPIO PL. +// Size is 36 bytes. +type gpioGroup struct { + // Pn_CFGx n*0x24+x*4 Port n Configure Register x (n from 1(B) to 7(H)) + cfg [4]uint32 + // Pn_DAT n*0x24+0x10 Port n Data Register (n from 1(B) to 7(H)) + data uint32 + // Pn_DRVx n*0x24+0x14+x*4 Port n Multi-Driving Register x (n from 1 to 7) + drv [2]uint32 + // Pn_PULL n*0x24+0x1C+x*4 Port n Pull Register (n from 1(B) to 7(H)) + pull [2]uint32 +} + +// gpioMap memory-maps all the gpio pin groups. +type gpioMap struct { + // PA to PI. + groups [9]gpioGroup +} + +// driverGPIO implements periph.Driver. +type driverGPIO struct { + // gpioMemory is the memory map of the CPU GPIO registers. + gpioMemory *gpioMap +} + +func (d *driverGPIO) String() string { + return "allwinner-gpio" +} + +func (d *driverGPIO) Prerequisites() []string { + return nil +} + +func (d *driverGPIO) After() []string { + return []string{"sysfs-gpio"} +} + +// Init does nothing if an allwinner processor is not detected. If one is +// detected, it memory maps gpio CPU registers and then sets up the pin mapping +// for the exact processor model detected. +func (d *driverGPIO) Init() (bool, error) { + if !Present() { + return false, errors.New("Allwinner CPU not detected") + } + + // Mark the right pins as available even if the memory map fails so they can + // callback to sysfs.Pins. + switch { + case IsA64(): + if err := mapA64Pins(); err != nil { + return true, err + } + case IsR8(): + if err := mapR8Pins(); err != nil { + return true, err + } + case IsA20(): + if err := mapA20Pins(); err != nil { + return true, err + } + default: + return false, errors.New("unknown Allwinner CPU model") + } + + // gpioBaseAddr is the physical base address of the GPIO registers. + gpioBaseAddr := uint32(getBaseAddress()) + if err := pmem.MapAsPOD(uint64(gpioBaseAddr), &d.gpioMemory); err != nil { + if os.IsPermission(err) { + return true, fmt.Errorf("need more access, try as root: %v", err) + } + return true, err + } + + return true, initPins() +} + +func init() { + if isArm { + periph.MustRegister(&drvGPIO) + } +} + +// getBaseAddress queries the virtual file system to retrieve the base address +// of the GPIO registers for GPIO pins in groups PA to PI. +// +// Defaults to 0x01C20800 as per datasheet if it could not query the file +// system. +func getBaseAddress() uint64 { + base := uint64(0x01C20800) + link, err := os.Readlink("/sys/bus/platform/drivers/sun50i-pinctrl/driver") + if err != nil { + return base + } + parts := strings.SplitN(path.Base(link), ".", 2) + if len(parts) != 2 { + return base + } + base2, err := strconv.ParseUint(parts[0], 16, 64) + if err != nil { + return base + } + return base2 +} + +var drvGPIO driverGPIO + +// Ensure that the various structs implement the interfaces they're supposed to. +var _ gpio.PinIO = &Pin{} +var _ gpio.PinIn = &Pin{} +var _ gpio.PinOut = &Pin{} +var _ pin.PinFunc = &Pin{} diff --git a/vendor/periph.io/x/periph/host/allwinner/gpio_pl.go b/vendor/periph.io/x/periph/host/allwinner/gpio_pl.go new file mode 100644 index 0000000..847eab6 --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/gpio_pl.go @@ -0,0 +1,563 @@ +// Copyright 2016 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 allwinner + +import ( + "errors" + "fmt" + "os" + "path" + "strconv" + "strings" + "time" + + "periph.io/x/periph" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/gpio/gpioreg" + "periph.io/x/periph/conn/physic" + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/host/pmem" + "periph.io/x/periph/host/sysfs" +) + +// All the pins in the PL group. +var PL0, PL1, PL2, PL3, PL4, PL5, PL6, PL7, PL8, PL9, PL10, PL11, PL12 *PinPL + +// PinPL defines one CPU supported pin in the PL group. +// +// PinPL implements gpio.PinIO. +type PinPL struct { + // Immutable. + offset uint8 // as per register offset calculation + name string // name as per datasheet + defaultPull gpio.Pull // default pull at startup + + // Immutable after driver initialization. + sysfsPin *sysfs.Pin // Set to the corresponding sysfs.Pin, if any. + available bool // Set when the pin is available on this CPU architecture. + + // Mutable. + usingEdge bool // Set when edge detection is enabled. +} + +// String implements conn.Resource. +// +// It returns the pin name and number, ex: "PL5(352)". +func (p *PinPL) String() string { + return fmt.Sprintf("%s(%d)", p.name, p.Number()) +} + +// Halt implements conn.Resource. +// +// It stops edge detection if enabled. +func (p *PinPL) Halt() error { + if p.usingEdge { + if err := p.sysfsPin.Halt(); err != nil { + return p.wrap(err) + } + p.usingEdge = false + } + return nil +} + +// Name implements pin.Pin. +// +// It returns the pin name, ex: "PL5". +func (p *PinPL) Name() string { + return p.name +} + +// Number implements pin.Pin. +// +// It returns the GPIO pin number as represented by gpio sysfs. +func (p *PinPL) Number() int { + return 11*32 + int(p.offset) +} + +// Function implements pin.Pin. +func (p *PinPL) Function() string { + return string(p.Func()) +} + +// Func implements pin.PinFunc. +func (p *PinPL) Func() pin.Func { + if !p.available { + // We do not want the error message about uninitialized system. + return pin.FuncNone + } + if drvGPIOPL.gpioMemoryPL == nil { + if p.sysfsPin == nil { + return pin.FuncNone + } + return p.sysfsPin.Func() + } + switch f := p.function(); f { + case in: + if p.FastRead() { + return gpio.IN_HIGH + } + return gpio.IN_LOW + case out: + if p.FastRead() { + return gpio.OUT_HIGH + } + return gpio.OUT_LOW + case alt1: + if s := mappingPL[p.offset][0]; len(s) != 0 { + return pin.Func(s) + } + return pin.Func("ALT1") + case alt2: + if s := mappingPL[p.offset][1]; len(s) != 0 { + return pin.Func(s) + } + return pin.Func("ALT2") + case alt3: + if s := mappingPL[p.offset][2]; len(s) != 0 { + return pin.Func(s) + } + return pin.Func("ALT3") + case alt4: + if s := mappingPL[p.offset][3]; len(s) != 0 { + return pin.Func(s) + } + return pin.Func("ALT4") + case alt5: + if s := mappingPL[p.offset][4]; len(s) != 0 { + if strings.Contains(string(s), "_EINT") { + // It's an input supporting interrupts. + if p.FastRead() { + return gpio.IN_HIGH + } + return gpio.IN_LOW + } + return pin.Func(s) + } + return pin.Func("ALT5") + case disabled: + return pin.FuncNone + default: + return pin.FuncNone + } +} + +// SupportedFuncs implements pin.PinFunc. +func (p *PinPL) SupportedFuncs() []pin.Func { + f := make([]pin.Func, 0, 2+2) + f = append(f, gpio.IN, gpio.OUT) + for _, m := range mappingPL[p.offset] { + if m != pin.FuncNone && !strings.Contains(string(m), "_EINT") { + f = append(f, m) + } + } + return f +} + +// SetFunc implements pin.PinFunc. +func (p *PinPL) SetFunc(f pin.Func) error { + switch f { + case gpio.FLOAT: + return p.In(gpio.Float, gpio.NoEdge) + case gpio.IN: + return p.In(gpio.PullNoChange, gpio.NoEdge) + case gpio.IN_LOW: + return p.In(gpio.PullDown, gpio.NoEdge) + case gpio.IN_HIGH: + return p.In(gpio.PullUp, gpio.NoEdge) + case gpio.OUT_HIGH: + return p.Out(gpio.High) + case gpio.OUT_LOW: + return p.Out(gpio.Low) + default: + isGeneral := f == f.Generalize() + for i, m := range mappingPL[p.offset] { + if m == f || (isGeneral && m.Generalize() == f) { + if err := p.Halt(); err != nil { + return err + } + switch i { + case 0: + p.setFunction(alt1) + case 1: + p.setFunction(alt2) + case 2: + p.setFunction(alt3) + case 3: + p.setFunction(alt4) + case 4: + p.setFunction(alt5) + } + return nil + } + } + return p.wrap(errors.New("unsupported function")) + } +} + +// In implements gpio.PinIn. +func (p *PinPL) In(pull gpio.Pull, edge gpio.Edge) error { + if !p.available { + // We do not want the error message about uninitialized system. + return p.wrap(errors.New("not available on this CPU architecture")) + } + if p.usingEdge && edge == gpio.NoEdge { + if err := p.sysfsPin.Halt(); err != nil { + return p.wrap(err) + } + p.usingEdge = false + } + if drvGPIOPL.gpioMemoryPL == nil { + if p.sysfsPin == nil { + return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible; try running as root?")) + } + if pull != gpio.PullNoChange { + return p.wrap(errors.New("pull cannot be used when subsystem gpiomem not initialized; try running as root?")) + } + if err := p.sysfsPin.In(pull, edge); err != nil { + return p.wrap(err) + } + p.usingEdge = edge != gpio.NoEdge + return nil + } + if !p.setFunction(in) { + return p.wrap(errors.New("failed to set pin as input")) + } + if pull != gpio.PullNoChange { + off := p.offset / 16 + shift := 2 * (p.offset % 16) + // Do it in a way that is concurrent safe. + drvGPIOPL.gpioMemoryPL.pull[off] &^= 3 << shift + switch pull { + case gpio.PullDown: + drvGPIOPL.gpioMemoryPL.pull[off] = 2 << shift + case gpio.PullUp: + drvGPIOPL.gpioMemoryPL.pull[off] = 1 << shift + default: + } + } + if edge != gpio.NoEdge { + if p.sysfsPin == nil { + return p.wrap(fmt.Errorf("pin %d is not exported by sysfs", p.Number())) + } + // This resets pending edges. + if err := p.sysfsPin.In(gpio.PullNoChange, edge); err != nil { + return p.wrap(err) + } + p.usingEdge = true + } + return nil +} + +// Read implements gpio.PinIn. +func (p *PinPL) Read() gpio.Level { + if drvGPIOPL.gpioMemoryPL == nil { + if p.sysfsPin == nil { + return gpio.Low + } + return p.sysfsPin.Read() + } + return gpio.Level(drvGPIOPL.gpioMemoryPL.data&(1<> (2 * (p.offset % 16))) & 3 { + case 0: + return gpio.Float + case 1: + return gpio.PullUp + case 2: + return gpio.PullDown + default: + // Confused. + return gpio.PullNoChange + } +} + +// DefaultPull implements gpio.PinIn. +func (p *PinPL) DefaultPull() gpio.Pull { + return p.defaultPull +} + +// Out implements gpio.PinOut. +func (p *PinPL) Out(l gpio.Level) error { + if !p.available { + // We do not want the error message about uninitialized system. + return p.wrap(errors.New("not available on this CPU architecture")) + } + if drvGPIOPL.gpioMemoryPL == nil { + if p.sysfsPin != nil { + return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible; try running as root?")) + } + return p.sysfsPin.Out(l) + } + // First disable edges. + if err := p.Halt(); err != nil { + return err + } + p.FastOut(l) + if !p.setFunction(out) { + return p.wrap(errors.New("failed to set pin as output")) + } + return nil +} + +// FastOut sets a pin output level with Absolutely No error checking. +// +// See Pin.FastOut for more information. +func (p *PinPL) FastOut(l gpio.Level) { + bit := uint32(1 << p.offset) + if l { + drvGPIOPL.gpioMemoryPL.data |= bit + } else { + drvGPIOPL.gpioMemoryPL.data &^= bit + } +} + +// PWM implements gpio.PinOut. +func (p *PinPL) PWM(gpio.Duty, physic.Frequency) error { + // TODO(maruel): PWM support for PL10. + return p.wrap(errors.New("not available on this CPU architecture")) +} + +// + +// function returns the current GPIO pin function. +// +// It must not be called if drvGPIOPL.gpioMemoryPL is nil. +func (p *PinPL) function() function { + shift := 4 * (p.offset % 8) + return function((drvGPIOPL.gpioMemoryPL.cfg[p.offset/8] >> shift) & 7) +} + +// setFunction changes the GPIO pin function. +// +// Returns false if the pin was in AltN. Only accepts in and out +// +// It must not be called if drvGPIOPL.gpioMemoryPL is nil. +func (p *PinPL) setFunction(f function) bool { + if f != in && f != out { + return false + } + // Interrupt based edge triggering is Alt5 but this is only supported on some + // pins. + // TODO(maruel): This check should use a whitelist of pins. + if actual := p.function(); actual != in && actual != out && actual != disabled && actual != alt5 { + // Pin is in special mode. + return false + } + off := p.offset / 8 + shift := 4 * (p.offset % 8) + mask := uint32(disabled) << shift + v := (uint32(f) << shift) ^ mask + // First disable, then setup. This is concurrent safe. + drvGPIOPL.gpioMemoryPL.cfg[off] |= mask + drvGPIOPL.gpioMemoryPL.cfg[off] &^= v + if p.function() != f { + panic(f) + } + return true +} + +func (p *PinPL) wrap(err error) error { + return fmt.Errorf("allwinner-gpio-pl (%s): %v", p, err) +} + +// + +// cpuPinsPL is all the pins as supported by the CPU. There is no guarantee that +// they are actually connected to anything on the board. +var cpuPinsPL = []PinPL{ + {offset: 0, name: "PL0", defaultPull: gpio.PullUp}, + {offset: 1, name: "PL1", defaultPull: gpio.PullUp}, + {offset: 2, name: "PL2", defaultPull: gpio.Float}, + {offset: 3, name: "PL3", defaultPull: gpio.Float}, + {offset: 4, name: "PL4", defaultPull: gpio.Float}, + {offset: 5, name: "PL5", defaultPull: gpio.Float}, + {offset: 6, name: "PL6", defaultPull: gpio.Float}, + {offset: 7, name: "PL7", defaultPull: gpio.Float}, + {offset: 8, name: "PL8", defaultPull: gpio.Float}, + {offset: 9, name: "PL9", defaultPull: gpio.Float}, + {offset: 10, name: "PL10", defaultPull: gpio.Float}, + {offset: 11, name: "PL11", defaultPull: gpio.Float}, + {offset: 12, name: "PL12", defaultPull: gpio.Float}, +} + +// See gpio.go for details. +var mappingPL = [13][5]pin.Func{ + {"RSB_SCK", "I2C_SCL", "", "", "PL_EINT0"}, // PL0 + {"RSB_SDA", "I2C_SDA", "", "", "PL_EINT1"}, // PL1 + {"UART_TX", "", "", "", "PL_EINT2"}, // PL2 + {"UART_RX", "", "", "", "PL_EINT3"}, // PL3 + {"JTAG_TMS", "", "", "", "PL_EINT4"}, // PL4 + {"JTAG_TCK", "", "", "", "PL_EINT5"}, // PL5 + {"JTAG_TDO", "", "", "", "PL_EINT6"}, // PL6 + {"JTAG_TDI", "", "", "", "PL_EINT7"}, // PL7 + {"I2C_SCL", "", "", "", "PL_EINT8"}, // PL8 + {"I2C_SDA", "", "", "", "PL_EINT9"}, // PL9 + {"PWM0", "", "", "", "PL_EINT10"}, // PL10 + {"CIR_RX", "", "", "", "PL_EINT11"}, // PL11 + {"", "", "", "", "PL_EINT12"}, // PL12 +} + +func init() { + PL0 = &cpuPinsPL[0] + PL1 = &cpuPinsPL[1] + PL2 = &cpuPinsPL[2] + PL3 = &cpuPinsPL[3] + PL4 = &cpuPinsPL[4] + PL5 = &cpuPinsPL[5] + PL6 = &cpuPinsPL[6] + PL7 = &cpuPinsPL[7] + PL8 = &cpuPinsPL[8] + PL9 = &cpuPinsPL[9] + PL10 = &cpuPinsPL[10] + PL11 = &cpuPinsPL[11] + PL12 = &cpuPinsPL[12] +} + +// getBaseAddressPL queries the virtual file system to retrieve the base address +// of the GPIO registers for GPIO pins in group PL. +// +// Defaults to 0x01F02C00 as per datasheet if could query the file system. +func getBaseAddressPL() uint64 { + base := uint64(0x01F02C00) + link, err := os.Readlink("/sys/bus/platform/drivers/sun50i-r-pinctrl/driver") + if err != nil { + return base + } + parts := strings.SplitN(path.Base(link), ".", 2) + if len(parts) != 2 { + return base + } + base2, err := strconv.ParseUint(parts[0], 16, 64) + if err != nil { + return base + } + return base2 +} + +// driverGPIOPL implements periph.Driver. +type driverGPIOPL struct { + // gpioMemoryPL is only the PL group in that case. Note that groups PI, PJ, PK + // do not exist. + gpioMemoryPL *gpioGroup +} + +func (d *driverGPIOPL) String() string { + return "allwinner-gpio-pl" +} + +func (d *driverGPIOPL) Prerequisites() []string { + return nil +} + +func (d *driverGPIOPL) After() []string { + return []string{"sysfs-gpio"} +} + +func (d *driverGPIOPL) Init() (bool, error) { + // BUG(maruel): H3 supports group PL too. + if !IsA64() { + return false, errors.New("A64 CPU not detected") + } + + // Mark the right pins as available even if the memory map fails so they can + // callback to sysfs.Pins. + functions := map[pin.Func]struct{}{} + for i := range cpuPinsPL { + name := cpuPinsPL[i].Name() + num := strconv.Itoa(cpuPinsPL[i].Number()) + cpuPinsPL[i].available = true + gpion := "GPIO" + num + + // Unregister the pin if already registered. This happens with sysfs-gpio. + // Do not error on it, since sysfs-gpio may have failed to load. + _ = gpioreg.Unregister(gpion) + _ = gpioreg.Unregister(num) + + // Register the pin with gpio. + if err := gpioreg.Register(&cpuPinsPL[i]); err != nil { + return true, err + } + if err := gpioreg.RegisterAlias(gpion, name); err != nil { + return true, err + } + if err := gpioreg.RegisterAlias(num, name); err != nil { + return true, err + } + switch f := cpuPinsPL[i].Func(); f { + case gpio.IN, gpio.OUT, pin.FuncNone: + default: + // Registering the same alias twice fails. This can happen if two pins + // are configured with the same function. + if _, ok := functions[f]; !ok { + // TODO(maruel): We'd have to clear out the ones from allwinner-gpio + // too. + functions[f] = struct{}{} + _ = gpioreg.RegisterAlias(string(f), name) + } + } + } + + // Now do a second loop but do the alternate functions. + for i := range cpuPinsPL { + for _, f := range cpuPinsPL[i].SupportedFuncs() { + switch f { + case gpio.IN, gpio.OUT: + default: + if _, ok := functions[f]; !ok { + // TODO(maruel): We'd have to clear out the ones from allwinner-gpio + // too. + functions[f] = struct{}{} + _ = gpioreg.RegisterAlias(string(f), cpuPinsPL[i].name) + } + } + } + } + + m, err := pmem.Map(getBaseAddressPL(), 4096) + if err != nil { + if os.IsPermission(err) { + return true, fmt.Errorf("need more access, try as root: %v", err) + } + return true, err + } + if err := m.AsPOD(&d.gpioMemoryPL); err != nil { + return true, err + } + + return true, nil +} + +func init() { + if isArm { + periph.MustRegister(&drvGPIOPL) + } +} + +var drvGPIOPL driverGPIOPL + +var _ gpio.PinIO = &PinPL{} +var _ gpio.PinIn = &PinPL{} +var _ gpio.PinOut = &PinPL{} +var _ pin.PinFunc = &PinPL{} diff --git a/vendor/periph.io/x/periph/host/allwinner/pwm.go b/vendor/periph.io/x/periph/host/allwinner/pwm.go new file mode 100644 index 0000000..92e0db6 --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/pwm.go @@ -0,0 +1,197 @@ +// Copyright 2016 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 allwinner + +import ( + "fmt" + "strings" + "time" +) + +const pwmClock = 24000000 +const pwmMaxPeriod = 0x10000 + +// prescalers is the value for pwm0Prescale* +var prescalers = []struct { + freq uint32 + scaler pwmPrescale +}{ + // Base frequency (min freq is half that) / PWM clock at pwmMaxPeriod + {pwmClock, pwmPrescale1}, // 24MHz / 366Hz + {pwmClock / 120, pwmPrescale120}, // 200kHz / 3Hz + {pwmClock / 180, pwmPrescale180}, // 133kHz / 2Hz + {pwmClock / 240, pwmPrescale240}, // 100kHz / 1.5Hz + {pwmClock / 360, pwmPrescale360}, // 66kHz / 1.01Hz + {pwmClock / 480, pwmPrescale480}, // 50kHz / 0.7Hz + {pwmClock / 12000, pwmPrescale12000}, // 2kHz + {pwmClock / 24000, pwmPrescale24000}, // 1kHz + {pwmClock / 36000, pwmPrescale36000}, // 666 Hz + {pwmClock / 48000, pwmPrescale48000}, // 500 Hz + {pwmClock / 72000, pwmPrescale72000}, // 333 Hz / 0.005Hz +} + +const ( + // 31:29 reserved + pwmBusy pwmCtl = 1 << 28 // PWM0_RDY + // 27:10 reserved (used for pwm1) + pwm0Mask pwmCtl = (1 << 10) - 1 + pwm0Bypass pwmCtl = 1 << 9 // PWM0_BYPASS (marked as unused on some drivers?) + pwm0PulseStart pwmCtl = 1 << 8 // PWM_CH0_PUL_START + pwm0ModePulse pwmCtl = 1 << 7 // PWM_CHANNEL0_MODE + pwm0SCLK pwmCtl = 1 << 6 // SCLK_CH0_GATING + pwm0Polarity pwmCtl = 1 << 5 // PWM_CH0_ACT_STA + pwm0Enable pwmCtl = 1 << 4 // PWM_CH0_EN + // 3:0 + pwm0PrescaleMask pwmCtl = pwmCtl(pwmPrescaleMask) // PWM_CH0_PRESCAL + pwm0Prescale120 pwmCtl = pwmCtl(pwmPrescale120) + pwm0Prescale180 pwmCtl = pwmCtl(pwmPrescale180) + pwm0Prescale240 pwmCtl = pwmCtl(pwmPrescale240) + pwm0Prescale360 pwmCtl = pwmCtl(pwmPrescale360) + pwm0Prescale480 pwmCtl = pwmCtl(pwmPrescale480) + // 5, 6, 7 reserved + pwm0Prescale12000 pwmCtl = pwmCtl(pwmPrescale12000) + pwm0Prescale24000 pwmCtl = pwmCtl(pwmPrescale24000) + pwm0Prescale36000 pwmCtl = pwmCtl(pwmPrescale36000) + pwm0Prescale48000 pwmCtl = pwmCtl(pwmPrescale48000) + pwm0Prescale72000 pwmCtl = pwmCtl(pwmPrescale72000) + // 13, 14 reserved + pwm0Prescale1 pwmCtl = pwmCtl(pwmPrescale1) +) + +// A64: Pages 194-195. +// R8: Pages 83-84. +type pwmCtl uint32 + +func (p pwmCtl) String() string { + var out []string + if p&pwmBusy != 0 { + out = append(out, "PWM0_RDY") + p &^= pwmBusy + } + if p&pwm0Bypass != 0 { + out = append(out, "PWM0_BYPASS") + p &^= pwm0Bypass + } + if p&pwm0PulseStart != 0 { + out = append(out, "PWM0_CH0_PUL_START") + p &^= pwm0PulseStart + } + if p&pwm0ModePulse != 0 { + out = append(out, "PWM0_CHANNEL0_MODE") + p &^= pwm0ModePulse + } + if p&pwm0SCLK != 0 { + out = append(out, "SCLK_CH0_GATING") + p &^= pwm0SCLK + } + if p&pwm0Polarity != 0 { + out = append(out, "PWM_CH0_ACT_STA") + p &^= pwm0Polarity + } + if p&pwm0Enable != 0 { + out = append(out, "PWM_CH0_EN") + p &^= pwm0Enable + } + out = append(out, pwmPrescale(p&pwm0PrescaleMask).String()) + p &^= pwm0PrescaleMask + if p != 0 { + out = append(out, fmt.Sprintf("Unknown(0x%08X)", uint32(p))) + } + return strings.Join(out, "|") +} + +const ( + pwmPrescaleMask pwmPrescale = 0xF + pwmPrescale120 pwmPrescale = 0 + pwmPrescale180 pwmPrescale = 1 + pwmPrescale240 pwmPrescale = 2 + pwmPrescale360 pwmPrescale = 3 + pwmPrescale480 pwmPrescale = 4 + // 5, 6, 7 reserved + pwmPrescale12000 pwmPrescale = 8 + pwmPrescale24000 pwmPrescale = 9 + pwmPrescale36000 pwmPrescale = 10 + pwmPrescale48000 pwmPrescale = 11 + pwmPrescale72000 pwmPrescale = 12 + // 13, 14 reserved + pwmPrescale1 pwmPrescale = 15 +) + +type pwmPrescale uint32 + +func (p pwmPrescale) String() string { + switch p { + case pwmPrescale120: + return "/120" + case pwmPrescale180: + return "/180" + case pwmPrescale240: + return "/240" + case pwmPrescale360: + return "/360" + case pwmPrescale480: + return "/480" + case pwmPrescale12000: + return "/12k" + case pwmPrescale24000: + return "/24k" + case pwmPrescale36000: + return "/36k" + case pwmPrescale48000: + return "/48k" + case pwmPrescale72000: + return "/72k" + case pwmPrescale1: + return "/1" + default: + return fmt.Sprintf("InvalidScalar(%d)", p&pwmPrescaleMask) + } +} + +// A64: Page 195. +// R8: Page 84 +type pwmPeriod uint32 + +func (p pwmPeriod) String() string { + return fmt.Sprintf("%d/%d", p&0xFFFF, uint32((p>>16)&0xFFFF)+1) +} + +func toPeriod(total uint32, active uint16) pwmPeriod { + if total > pwmMaxPeriod { + total = pwmMaxPeriod + } + return pwmPeriod(total-1)<<16 | pwmPeriod(active) +} + +// getBestPrescale finds the best prescaler. +// +// Cycles must be between 2 and 0x10000/2. +func getBestPrescale(period time.Duration) pwmPrescale { + // TODO(maruel): Rewrite this function, it is incorrect. + for _, v := range prescalers { + p := time.Second / time.Duration(v.freq) + smallest := (period / pwmMaxPeriod) + largest := (period / 2) + if p > smallest && p < largest { + return v.scaler + } + } + // Period is longer than 196s. + return pwmPrescale72000 +} + +// pwmMap represents the PWM memory mapped CPU registers. +// +// The base frequency is 24Mhz. +// +// TODO(maruel): Some CPU have 2 PWMs. +type pwmMap struct { + ctl pwmCtl // PWM_CTRL_REG + period pwmPeriod // PWM_CH0_PERIOD +} + +func (p *pwmMap) String() string { + return fmt.Sprintf("pwmMap{%s, %v}", p.ctl, p.period) +} diff --git a/vendor/periph.io/x/periph/host/allwinner/r8.go b/vendor/periph.io/x/periph/host/allwinner/r8.go new file mode 100644 index 0000000..7790763 --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/r8.go @@ -0,0 +1,149 @@ +// Copyright 2016 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. + +// This file contains pin mapping information that is specific to the Allwinner +// R8 model. + +package allwinner + +import ( + "strings" + + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/host/sysfs" +) + +// R8 specific pins. +var ( + FEL *pin.BasicPin // Boot mode selection + MIC_IN *pin.BasicPin // Microphone in + MIC_GND *pin.BasicPin // Microphone ground + HP_LEFT *pin.BasicPin // Left speaker out + HP_RIGHT *pin.BasicPin // Right speaker out + HP_COM *pin.BasicPin // Speaker common + X1, X2, Y1, Y2 *pin.BasicPin // Touch screen pins +) + +// + +func init() { + FEL = &pin.BasicPin{N: "FEL"} + MIC_IN = &pin.BasicPin{N: "MIC_IN"} + MIC_GND = &pin.BasicPin{N: "MIC_GND"} + HP_LEFT = &pin.BasicPin{N: "HP_LEFT"} + HP_RIGHT = &pin.BasicPin{N: "HP_RIGHT"} + HP_COM = &pin.BasicPin{N: "HP_COM"} + + X1 = &pin.BasicPin{N: "X1"} + X2 = &pin.BasicPin{N: "X2"} + Y1 = &pin.BasicPin{N: "Y1"} + Y2 = &pin.BasicPin{N: "Y2"} +} + +// mappingR8 describes the mapping of each R8 processor gpio to their alternate +// functions. +// +// It omits the in & out functions which are available on all pins. +// +// The mapping comes from the datasheet page 18: +// https://github.com/NextThingCo/CHIP-Hardware/raw/master/CHIP%5Bv1_0%5D/CHIPv1_0-BOM-Datasheets/Allwinner%20R8%20Datasheet%20V1.2.pdf +// +// - The datasheet uses TWI instead of I2C but this is renamed here for consistency. +var mappingR8 = map[string][5]pin.Func{ + "PB0": {"I2C0_SCL"}, + "PB1": {"I2C0_SDA"}, + "PB2": {"PWM0", "", "", "", "EINT16"}, + "PB3": {"IR_TX", "", "", "", "EINT17"}, + "PB4": {"IR_RX", "", "", "", "EINT18"}, + "PB10": {"SPI2_CS1"}, + "PB15": {"I2C1_SCL"}, + "PB16": {"I2C1_SDA"}, + "PB17": {"I2C2_SCL"}, + "PB18": {"I2C2_SDA"}, + "PC0": {"NAND_WE", "SPI0_MOSI"}, + "PC1": {"NAND_ALE", "SPI0_MISO"}, + "PC2": {"NAND_CLE", "SPI0_CLK"}, + "PC3": {"NAND_CE1", "SPI0_CS0"}, + "PC4": {"NAND_CE0"}, + "PC5": {"NAND_RE"}, + "PC6": {"NAND_RB0", "SDC2_CMD"}, + "PC7": {"NAND_RB1", "SDC2_CLK"}, + "PC8": {"NAND_DQ0", "SDC2_D0"}, + "PC9": {"NAND_DQ1", "SDC2_D1"}, + "PC10": {"NAND_DQ2", "SDC2_D2"}, + "PC11": {"NAND_DQ3", "SDC2_D3"}, + "PC12": {"NAND_DQ4", "SDC2_D4"}, + "PC13": {"NAND_DQ5", "SDC2_D5"}, + "PC14": {"NAND_DQ6", "SDC2_D6"}, + "PC15": {"NAND_DQ7", "SDC2_D7"}, + "PC19": {""}, + "PD2": {"LCD_D2", "UART2_TX"}, + "PD3": {"LCD_D3", "UART2_RX"}, + "PD4": {"LCD_D4", "UART2_CTX"}, + "PD5": {"LCD_D5", "UART2_RTS"}, + "PD6": {"LCD_D6", "ECRS"}, + "PD7": {"LCD_D7", "ECOL"}, + "PD10": {"LCD_D10", "ERXD0"}, + "PD11": {"LCD_D11", "ERXD1"}, + "PD12": {"LCD_D12", "ERXD2"}, + "PD13": {"LCD_D13", "ERXD3"}, + "PD14": {"LCD_D14", "ERXCK"}, + "PD15": {"LCD_D15", "ERXERR"}, + "PD18": {"LCD_D18", "ERXDV"}, + "PD19": {"LCD_D19", "ETXD0"}, + "PD20": {"LCD_D20", "ETXD1"}, + "PD21": {"LCD_D21", "ETXD2"}, + "PD22": {"LCD_D22", "ETXD3"}, + "PD23": {"LCD_D23", "ETXEN"}, + "PD24": {"LCD_CLK", "ETXCK"}, + "PD25": {"LCD_DE", "ETXERR"}, + "PD26": {"LCD_HSYNC", "EMDC"}, + "PD27": {"LCD_VSYNC", "EMDIO"}, + "PE0": {"TS_CLK", "CSI_PCLK", "SPI2_CS0", "", "EINT14"}, + "PE1": {"TS_ERR", "CSI_MCLK", "SPI2_CLK", "", "EINT15"}, + "PE2": {"TS_SYNC", "CSI_HSYNC", "SPI2_MOSI"}, + "PE3": {"TS_DVLD", "CSI_VSYNC", "SPI2_MISO"}, + "PE4": {"TS_D0", "CSI_D0", "SDC2_D0"}, + "PE5": {"TS_D1", "CSI_D1", "SDC2_D1"}, + "PE6": {"TS_D2", "CSI_D2", "SDC2_D2"}, + "PE7": {"TS_D3", "CSI_D3", "SDC2_D3"}, + "PE8": {"TS_D4", "CSI_D4", "SDC2_CMD"}, + "PE9": {"TS_D5", "CSI_D5", "SDC2_CLK"}, + "PE10": {"TS_D6", "CSI_D6", "UART1_TX"}, + "PE11": {"TS_D7", "CSI_D7", "UART1_RX"}, + "PF0": {"SDC0_D1", "", "JTAG1_TMS"}, + "PF1": {"SDC0_D0", "", "JTAG1_TDI"}, + "PF2": {"SDC0_CLK", "", "UART0_TX"}, + "PF3": {"SDC0_CMD", "", "JTAG1_TDO"}, + "PF4": {"SDC0_D3", "", "UART0_RX"}, + "PF5": {"SDC0_D2", "", "JTAG1_TCK"}, + "PG0": {"GPS_CLK", "", "", "", "EINT0"}, + "PG1": {"GPS_SIGN", "", "", "", "EINT1"}, + "PG2": {"GPS_MAG", "", "", "", "EINT2"}, + "PG3": {"", "", "UART1_TX", "", "EINT3"}, + "PG4": {"", "", "UART1_RX", "", "EINT4"}, + "PG9": {"SPI1_CS0", "UART3_TX", "", "", "EINT9"}, + "PG10": {"SPI1_CLK", "UART3_RX", "", "", "EINT10"}, + "PG11": {"SPI1_MOSI", "UART3_CTS", "", "", "EINT11"}, + "PG12": {"SPI1_MISO", "UART3_RTS", "", "", "EINT12"}, +} + +// mapR8Pins uses mappingR8 to actually set the altFunc fields of all gpio and +// mark them as available. +// +// It is called by the generic allwinner processor code if a R8 is detected. +func mapR8Pins() error { + for name, altFuncs := range mappingR8 { + pin := cpupins[name] + pin.altFunc = altFuncs + pin.available = true + if strings.Contains(string(altFuncs[4]), "EINT") { + pin.supportEdge = true + } + + // Initializes the sysfs corresponding pin right away. + pin.sysfsPin = sysfs.Pins[pin.Number()] + } + return nil +} diff --git a/vendor/periph.io/x/periph/host/allwinner/spi.go b/vendor/periph.io/x/periph/host/allwinner/spi.go new file mode 100644 index 0000000..ceb645e --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/spi.go @@ -0,0 +1,207 @@ +// Copyright 2016 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 allwinner + +import ( + "errors" + "fmt" + "log" + "time" +) + +const ( + // 31:20 reserved + // Set this bit to ‘1’ to make the internal read sample point with a delay of + // half cycle of SPI_CLK. It is used in high speed read operation to reduce + // the error caused by the time delay of SPI_CLK propagating between master + // and slave. + // 1 – delay internal read sample point + // 0 – normal operation, do not delay internal read sample point + spiR8HalfDelay spiR8Ctl = 1 << 19 // Master Sample Data Control + spiR8TransmitPause spiR8Ctl = 1 << 18 // Transmit Pause Enable + spiR8CSLevel spiR8Ctl = 1 << 17 // SS_LEVEL; Chip Select level + spiR8CSManual spiR8Ctl = 1 << 16 // SS_CTRL; Do not switch CS automatically + spiR8DiscardHash spiR8Ctl = 1 << 15 // DHB + spiR8DummyBurst spiR8Ctl = 1 << 14 // DDB + spiR8CS0 spiR8Ctl = 0 << 12 // SS; Which CS line to use. For SPI0 only + spiR8CS1 spiR8Ctl = 1 << 12 // + spiR8CS2 spiR8Ctl = 2 << 12 // + spiR8CS3 spiR8Ctl = 3 << 12 // + spiR8RapidsReadMode spiR8Ctl = 1 << 11 // RPSM + spiR8ExchangeBurst spiR8Ctl = 1 << 10 // XCH + spiR8RXFIFOReset spiR8Ctl = 1 << 9 // RXFIFO Reset; Write to reset the FIFO as empty + spiR8TXFIFOReset spiR8Ctl = 1 << 8 // TXFIFO Reset; Write to reset the FIFO as empty + spiR8CSBetweenBursts spiR8Ctl = 1 << 7 // SSCTL + spiR8LSB spiR8Ctl = 1 << 6 // LMTF; MSB by default, LSB when set + spiR8DDMA spiR8Ctl = 1 << 5 // DMAM; Use dedicated DMA if set, normal DMA otherwise + spiR8CSActiveLow spiR8Ctl = 1 << 4 // SSPOL; CS line polarity + spiR8ClkActiveLow spiR8Ctl = 1 << 3 // POL; Clock line polarity + spiR8PHA spiR8Ctl = 1 << 2 // PHA; Phase 1 if set (leading edge for setup data) + spiR8Master spiR8Ctl = 1 << 1 // MODE; Slave mode if not set + spiR8Enable spiR8Ctl = 1 << 0 // EN; Enable mode +) + +// SPI_CTL +// R8: Page 153-155. Default: 0x0002001C +type spiR8Ctl uint32 + +// SPI_INTCTL +// R8: Page 155-156. +type spiR8IntCtl uint32 + +const ( + spiR8ClearInterrupt spiR8IntStatus = 1 << 31 // Clear interrupt busy flag + // 30:18 reserved + spiR8InvalidSS spiR8IntStatus = 1 << 17 // SSI + spiR8TC spiR8IntStatus = 1 << 16 // TC; Transfer Completed +) + +// SPI_INT_STA +// R8: Page 156-157. +type spiR8IntStatus uint32 + +const ( + // 31:13 reserved + spiR8DMATX3Quarter spiR8DMACtl = 1 << 12 // TXFIFO 3/4 empty + spiR8DMATX1Quarter spiR8DMACtl = 1 << 11 // TXFIFO 1/4 empty + spiR8DMATXByte spiR8DMACtl = 1 << 10 // TXFIFO Not Full + spiR8DMATXHalf spiR8DMACtl = 1 << 9 // TXFIFO 1/2 empty + spiR8DMATXEmpty spiR8DMACtl = 1 << 8 // TXFIFO empty + // 7:5 reserved + spiR8DMARX3Quarter spiR8DMACtl = 1 << 4 // RXFIFO 3/4 empty + spiR8DMARX1Quarter spiR8DMACtl = 1 << 3 // RXFIFO 1/4 empty + spiR8DMARXByte spiR8DMACtl = 1 << 2 // RXFIFO Not Full + spiR8DMARXHalf spiR8DMACtl = 1 << 1 // RXFIFO 1/2 empty + spiR8DMARXEmpty spiR8DMACtl = 1 << 0 // RXFIFO empty +) + +// SPI_DMACTL +// R8: Page 158. +type spiR8DMACtl uint32 + +const ( + // 31:13 reserved + spiR8DivRateSelect2 spiR8ClockCtl = 1 << 12 // DRS; Use spiDivXX if set, use mask otherwise + spiR8Div2 spiR8ClockCtl = 0 << 8 // CDR1; Use divisor 2^(n+1) + spiR8Div4 spiR8ClockCtl = 1 << 8 // + spiR8Div8 spiR8ClockCtl = 2 << 8 // + spiR8Div16 spiR8ClockCtl = 3 << 8 // + spiR8Div32 spiR8ClockCtl = 4 << 8 // + spiR8Div64 spiR8ClockCtl = 5 << 8 // + spiR8Div128 spiR8ClockCtl = 6 << 8 // + spiR8Div256 spiR8ClockCtl = 7 << 8 // + spiR8Div512 spiR8ClockCtl = 8 << 8 // + spiR8Div1024 spiR8ClockCtl = 9 << 8 // + spiR8Div2048 spiR8ClockCtl = 10 << 8 // + spiR8Div4096 spiR8ClockCtl = 11 << 8 // + spiR8Div8192 spiR8ClockCtl = 12 << 8 // + spiR8Div16384 spiR8ClockCtl = 13 << 8 // + spiR8Div32768 spiR8ClockCtl = 14 << 8 // + spiR8Div65536 spiR8ClockCtl = 15 << 8 // + spiR8Div1Mask spiR8ClockCtl = 0xFF // CDR2; Use divisor 2*(n+1) +) + +// SPI_CCTL +// R8: Page 159. +type spiR8ClockCtl uint32 + +const ( + // 31:25 reserved + spiR8FIFOTXShift = 16 // 0 to 64 + // 15:7 reserved + spiR8FIFORXShift = 0 // 0 to 64 +) + +// SPI_FIFO_STA +// R8: Page 160. +type spiR8FIFOStatus uint32 + +func (s spiR8FIFOStatus) tx() uint8 { + return uint8((uint32(s) >> 16) & 127) +} + +func (s spiR8FIFOStatus) rx() uint8 { + return uint8(uint32(s) & 127) +} + +// spiR8Group is the mapping of SPI registers for one SPI controller. +// R8: Page 152-153. +type spiR8Group struct { + rx uint32 // 0x00 SPI_RX_DATA RX Data + tx uint32 // 0x04 SPI_TX_DATA TX Data + ctl spiR8Ctl // 0x08 SPI_CTL Control + intCtl spiR8IntCtl // 0x0C SPI_INTCTL Interrupt Control + status spiR8IntStatus // 0x10 SPI_ST Status + dmaCtl spiR8DMACtl // 0x14 SPI_DMACTL DMA Control + wait uint32 // 0x18 SPI_WAIT Clock Counter; 16 bits + clockCtl spiR8ClockCtl // 0x1C SPI_CCTL Clock Rate Control + burstCounter uint32 // 0x20 SPI_BC Burst Counter; 24 bits + transmitCounter uint32 // 0x24 SPI_TC Transmit Counter; 24 bits + fifoStatus spiR8FIFOStatus // 0x28 SPI_FIFO_STA FIFO Status + reserved [(0x1000 - 0x02C) / 4]uint32 +} + +func (s *spiR8Group) setup() { + s.intCtl = 0 + s.status = 0 + //s.dmaCtl = spiR8DMARXByte + s.dmaCtl = 0 + s.wait = 2 + s.clockCtl = spiR8DivRateSelect2 | spiR8Div1024 + // spiR8DDMA + s.ctl = spiR8CSManual | spiR8LSB | spiR8Master | spiR8Enable +} + +// spiMap is the mapping of SPI registers. +// R8: Page 152-153. +type spiMap struct { + groups [3]spiR8Group +} + +// spi2Write do a write on SPI2_MOSI via polling. +func spi2Write(w []byte) error { + if drvDMA.clockMemory == nil || drvDMA.spiMemory == nil { + return errors.New("subsystem not initialized") + } + // Make sure the source clock is disabled. Set it at 250kHz. + //drvDMA.clockMemory.spi2Clk &^= clockSPIEnable + drvDMA.clockMemory.spi2Clk |= clockSPIEnable + drvDMA.clockMemory.spi2Clk = clockSPIDiv8a | clockSPIDiv12b + ch := &drvDMA.spiMemory.groups[2] + ch.setup() + fmt.Printf("Setup done\n") + for i := 0; i < len(w)/4; i++ { + // TODO(maruel): Access it in 8bit mode. + ch.tx = uint32(w[0]) + for ch.fifoStatus.tx() == 0 { + log.Printf("Waiting for bit %# v\n", ch) + time.Sleep(time.Second) + } + } + fmt.Printf("Done\n") + return nil +} + +// spi2Read do a read on SPI2_MISO via polling. +func spi2Read(r []byte) error { + if drvDMA.clockMemory == nil || drvDMA.spiMemory == nil { + return errors.New("subsystem not initialized") + } + // Make sure the source clock is disabled. Set it at 250kHz. + //drvDMA.clockMemory.spi2Clk &^= clockSPIEnable + drvDMA.clockMemory.spi2Clk |= clockSPIEnable + drvDMA.clockMemory.spi2Clk = clockSPIDiv8a | clockSPIDiv12b + ch := &drvDMA.spiMemory.groups[2] + ch.setup() + for i := 0; i < len(r)/4; i++ { + ch.tx = 0 + for ch.status&spiR8TC == 0 { + } + // TODO(maruel): Access it in 8bit mode. + r[i] = uint8(ch.rx) + } + fmt.Printf("Done\n") + return nil +} diff --git a/vendor/periph.io/x/periph/host/allwinner/timer.go b/vendor/periph.io/x/periph/host/allwinner/timer.go new file mode 100644 index 0000000..f11f805 --- /dev/null +++ b/vendor/periph.io/x/periph/host/allwinner/timer.go @@ -0,0 +1,128 @@ +// Copyright 2016 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 allwinner + +import ( + "time" + + "periph.io/x/periph/host/cpu" +) + +// ReadTime returns the time on a monotonic timer. +// +// It only works if allwinner-dma successfully loaded. Otherwise it returns 0. +func ReadTime() time.Duration { + if drvDMA.timerMemory == nil { + return 0 + } + v := uint64(drvDMA.timerMemory.counterHigh)<<32 | uint64(drvDMA.timerMemory.counterLow) + if v == 0 { + // BUG(maruel): Implement using AVS_CNT0_REG on A64. + return 0 + } + // BUG(maruel): Assumes that counterCtrl & timerPLL6 is not set. + const tick = time.Microsecond / 24 + return time.Duration(v) * tick +} + +// Nanospin spins the CPU without calling into the kernel code if possible. +func Nanospin(t time.Duration) { + start := ReadTime() + if start == 0 { + // Use the slow generic version. + cpu.Nanospin(t) + return + } + for ReadTime()-start < t { + } +} + +// + +const ( + // 31:3 reserved + timerPLL6 timerCtrl = 2 << 1 // CONT64_CLK_SRC_SEL; OSC24M if not set; + timerReadLatchEnable timerCtrl = 1 << 1 // CONT64_RLATCH_EN; 1 to latch the counter to the registers + timerClear = 1 << 0 // CONT64_CLR_EN; clears the counter +) + +// R8: Page 96 +type timerCtrl uint32 + +// timerMap is the mapping of important registers across CPUs. +type timerMap struct { + reserved0 [0x80 / 4]uint32 // + cntCtl timerCtrl // 0x80 AVS_CNT_CTL_REG AVS Control Register + cnt0 uint32 // 0x84 AVS_CNT0_REG AVS Counter 0 Register + cnt1 uint32 // 0x88 AVS_CNT1_REG AVS Counter 1 Register + cndDrv uint32 // 0x8C AVS_CNT_DIV_REG AVS Divisor Register + reserved1 [0x10 / 4]uint32 // On R8 only. + counterCtrl timerCtrl // 0x0A0 COUNTER64_CTRL_REG 64-bit Counter control + counterLow uint32 // 0x0A4 COUNTER64_LOW_REG 64-bit Counter low + counterHigh uint32 // 0x0A8 COUNTER64_HI_REG 64-bit Counter high +} + +// A64: Page 161. +type timerMapA64 struct { + reserved0 uint32 // 0x0 TMR_IRQ_EN_REG Timer IRQ Enable Register + reserved1 uint32 // 0x4 TMR_IRQ_STA_REG Timer Status Register + reserved2 uint32 // 0x10 TMR0_CTRL_REG Timer 0 Control Register + reserved3 uint32 // 0x14 TMR0_INTV_VALUE_REG Timer 0 Interval Value Register + reserved4 uint32 // 0x18 TMR0_CUR_VALUE_REG Timer 0 Current Value Register + reserved5 uint32 // 0x20 TMR1_CTRL_REG Timer 1 Control Register + reserved6 uint32 // 0x24 TMR1_INTV_VALUE_REG Timer 1 Interval Value Register + reserved7 uint32 // 0x28 TMR1_CUR_VALUE_REG Timer 1 Current Value Register + cntCtl timerCtrl // 0x80 AVS_CNT_CTL_REG AVS Control Register + cnt0 uint32 // 0x84 AVS_CNT0_REG AVS Counter 0 Register + cnt1 uint32 // 0x88 AVS_CNT1_REG AVS Counter 1 Register + cndDrv uint32 // 0x8C AVS_CNT_DIV_REG AVS Divisor Register + reserved8 uint32 // 0xA0 WDOG0_IRQ_EN_REG Watchdog 0 IRQ Enable Register + reserved9 uint32 // 0xA4 WDOG0_IRQ_STA_REG Watchdog 0 Status Register + reserved10 uint32 // 0xB0 WDOG0_CTRL_REG Watchdog 0 Control Register + reserved11 uint32 // 0xB4 WDOG0_CFG_REG Watchdog 0 Configuration Register + reserved12 uint32 // 0xB8 WDOG0_MODE_REG Watchdog 0 Mode Register +} + +// R8: Page 85 +type timerMapR8 struct { + reserved0 uint32 // 0x000 ASYNC_TMR_IRQ_EN_REG Timer IRQ Enable + reserved1 uint32 // 0x004 ASYNC_TMR_IRQ_STAS_REG Timer Status + reserved2 [2]uint32 // 0x008-0x00C + reserved3 uint32 // 0x010 ASYNC_TMR0_CTRL_REG Timer 0 Control + reserved4 uint32 // 0x014 ASYNC_TMR0_INTV_VALUE_REG Timer 0 Interval Value + reserved5 uint32 // 0x018 ASYNC_TMR0_CURNT_VALUE_REG Timer 0 Current Value + reserved6 uint32 // 0x01C + reserved7 uint32 // 0x020 ASYNC_TMR1_CTRL_REG Timer 1 Control + reserved8 uint32 // 0x024 ASYNC_TMR1_INTV_VALUE_REG Timer 1 Interval Value + reserved9 uint32 // 0x028 ASYNC_TMR1_CURNT_VALUE_REG Timer 1 Current Value + reserved10 uint32 // 0x02C + reserved11 uint32 // 0x030 ASYNC_TMR2_CTRL_REG Timer 2 Control + reserved12 uint32 // 0x034 ASYNC_TMR2_INTV_VALUE_REG Timer 2 Interval Value + reserved13 uint32 // 0x038 ASYNC_TMR2_CURNT_VALUE_REG Timer 2 Current Value + reserved14 uint32 // 0x03C + reserved15 uint32 // 0x040 ASYNC_TMR3_CTRL_REG Timer 3 Control + reserved16 uint32 // 0x044 ASYNC_TMR3_INTV_VALUE_REG Timer 3 Interval Value + reserved17 [2]uint32 // 0x048-0x04C + reserved18 uint32 // 0x050 ASYNC_TMR4_CTRL_REG Timer 4 Control + reserved19 uint32 // 0x054 ASYNC_TMR4_INTV_VALUE_REG Timer 4 Interval Value + reserved20 uint32 // 0x058 ASYNC_TMR4_CURNT_VALUE_REG Timer 4 Current Value + reserved21 uint32 // 0x05C + reserved22 uint32 // 0x060 ASYNC_TMR5_CTRL_REG Timer 5 Control + reserved23 uint32 // 0x064 ASYNC_TMR5_INTV_VALUE_REG Timer 5 Interval Value + reserved24 uint32 // 0x068 ASYNC_TMR5_CURNT_VALUE_REG Timer 5 Current Value + reserved25 [5]uint32 // 0x06C-0x07C + cntCtl timerCtrl // 0x080 AVS_CNT_CTL_REG AVS Control Register + cnt0 uint32 // 0x084 AVS_CNT0_REG AVS Counter 0 Register + cnt1 uint32 // 0x088 AVS_CNT1_REG AVS Counter 1 Register + cndDiv uint32 // 0x08C AVS_CNT_DIVISOR_REG AVS Divisor + reserved26 uint32 // 0x090 WDOG_CTRL_REG + reserved27 uint32 // 0x094 WDOG_MODE_REG Watchdog Mode + reserved28 [2]uint32 // 0x098-0x09C + counterCtrl timerCtrl // 0x0A0 COUNTER64_CTRL_REG 64-bit Counter control + counterLow uint32 // 0x0A4 COUNTER64_LOW_REG 64-bit Counter low + counterHigh uint32 // 0x0A8 COUNTER64_HI_REG 64-bit Counter high + reserved29 [0x94]uint32 // 0x0AC-0x13C + reserved30 uint32 // 0x140 CPU_CFG_REG CPU configuration register +} diff --git a/vendor/periph.io/x/periph/host/am335x/am335x.go b/vendor/periph.io/x/periph/host/am335x/am335x.go new file mode 100644 index 0000000..686ce25 --- /dev/null +++ b/vendor/periph.io/x/periph/host/am335x/am335x.go @@ -0,0 +1,52 @@ +// 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 am335x + +import ( + "errors" + "strings" + + "periph.io/x/periph" + "periph.io/x/periph/host/distro" +) + +// Present returns true if a TM AM335x processor is detected. +func Present() bool { + if isArm { + return strings.HasPrefix(distro.DTModel(), "TI AM335x") + } + return false +} + +// driver implements periph.Driver. +type driver struct { +} + +func (d *driver) String() string { + return "am335x" +} + +func (d *driver) Prerequisites() []string { + return nil +} + +func (d *driver) After() []string { + return nil +} + +func (d *driver) Init() (bool, error) { + if !Present() { + return false, errors.New("am335x CPU not detected") + } + return true, nil +} + +func init() { + if isArm { + periph.MustRegister(&drv) + } +} + +var drv driver diff --git a/vendor/periph.io/x/periph/host/am335x/am335x_arm.go b/vendor/periph.io/x/periph/host/am335x/am335x_arm.go new file mode 100644 index 0000000..8d17758 --- /dev/null +++ b/vendor/periph.io/x/periph/host/am335x/am335x_arm.go @@ -0,0 +1,7 @@ +// 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 am335x + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/am335x/am335x_other.go b/vendor/periph.io/x/periph/host/am335x/am335x_other.go new file mode 100644 index 0000000..3f3dbea --- /dev/null +++ b/vendor/periph.io/x/periph/host/am335x/am335x_other.go @@ -0,0 +1,9 @@ +// 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. + +// +build !arm + +package am335x + +const isArm = false diff --git a/vendor/periph.io/x/periph/host/am335x/doc.go b/vendor/periph.io/x/periph/host/am335x/doc.go new file mode 100644 index 0000000..2967405 --- /dev/null +++ b/vendor/periph.io/x/periph/host/am335x/doc.go @@ -0,0 +1,28 @@ +// 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 am335x exposes functionality for the Texas Instruments Sitara AM335x +// processor family. +// +// This processor family is found on the BeagleBone. PRU-ICSS functionality is +// implemented in package pru. +// +// The GPIO pins of the AM335x CPU are grouped into 3 groups of 32 pins: GPIO0, +// GPIO1, and GPIO2. The CPU documentation refers to GPIO in the form of +// GPIOx_y. To get the absolute number, as exposed by sysfs, use 32*x+y to get +// the absolute number. +// +// Datasheet +// +// Technical Reference Manual +// https://www.ti.com/lit/ug/spruh73p/spruh73p.pdf +// +// Other +// +// Marketing page +// https://www.ti.com/processors/sitara/arm-cortex-a8/am335x/overview.html +// +// Family overview +// https://www.ti.com/lit/ds/symlink/am3359.pdf +package am335x diff --git a/vendor/periph.io/x/periph/host/bcm283x/bcm283x_arm.go b/vendor/periph.io/x/periph/host/bcm283x/bcm283x_arm.go new file mode 100644 index 0000000..f8c1f6c --- /dev/null +++ b/vendor/periph.io/x/periph/host/bcm283x/bcm283x_arm.go @@ -0,0 +1,7 @@ +// Copyright 2016 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 bcm283x + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/bcm283x/bcm283x_arm64.go b/vendor/periph.io/x/periph/host/bcm283x/bcm283x_arm64.go new file mode 100644 index 0000000..a9bf6a3 --- /dev/null +++ b/vendor/periph.io/x/periph/host/bcm283x/bcm283x_arm64.go @@ -0,0 +1,9 @@ +// Copyright 2016 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. + +// +build arm64 + +package bcm283x + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/bcm283x/bcm283x_other.go b/vendor/periph.io/x/periph/host/bcm283x/bcm283x_other.go new file mode 100644 index 0000000..6a122b3 --- /dev/null +++ b/vendor/periph.io/x/periph/host/bcm283x/bcm283x_other.go @@ -0,0 +1,9 @@ +// Copyright 2016 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. + +// +build !arm,!arm64 + +package bcm283x + +const isArm = false diff --git a/vendor/periph.io/x/periph/host/bcm283x/clock.go b/vendor/periph.io/x/periph/host/bcm283x/clock.go new file mode 100644 index 0000000..f1f084c --- /dev/null +++ b/vendor/periph.io/x/periph/host/bcm283x/clock.go @@ -0,0 +1,330 @@ +// 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 bcm283x + +import ( + "errors" + "fmt" + "strings" + "time" + + "periph.io/x/periph/conn/physic" +) + +// errClockRegister is returned in a situation where the clock memory is not +// working as expected. It is mocked in tests. +var errClockRegister = errors.New("can't write to clock divisor CPU register") + +// Clock sources frequency in hertz. +const ( + clk19dot2MHz = 19200 * physic.KiloHertz + clk500MHz = 500 * physic.MegaHertz +) + +const ( + // 31:24 password + clockPasswdCtl clockCtl = 0x5A << 24 // PASSWD + // 23:11 reserved + clockMashMask clockCtl = 3 << 9 // MASH + clockMash0 clockCtl = 0 << 9 // src_freq / divI (ignores divF) + clockMash1 clockCtl = 1 << 9 + clockMash2 clockCtl = 2 << 9 + clockMash3 clockCtl = 3 << 9 // will cause higher spread + clockFlip clockCtl = 1 << 8 // FLIP + clockBusy clockCtl = 1 << 7 // BUSY + // 6 reserved + clockKill clockCtl = 1 << 5 // KILL + clockEnable clockCtl = 1 << 4 // ENAB + clockSrcMask clockCtl = 0xF << 0 // SRC + clockSrcGND clockCtl = 0 // 0Hz + clockSrc19dot2MHz clockCtl = 1 // 19.2MHz + clockSrcTestDebug0 clockCtl = 2 // 0Hz + clockSrcTestDebug1 clockCtl = 3 // 0Hz + clockSrcPLLA clockCtl = 4 // 0Hz + clockSrcPLLC clockCtl = 5 // 1000MHz (changes with overclock settings) + clockSrcPLLD clockCtl = 6 // 500MHz + clockSrcHDMI clockCtl = 7 // 216MHz; may be disabled + // 8-15 == GND. +) + +// clockCtl controls the clock properties. +// +// It must not be changed while busy is set or a glitch may occur. +// +// Page 107 +type clockCtl uint32 + +func (c clockCtl) String() string { + var out []string + if c&0xFF000000 == clockPasswdCtl { + c &^= 0xFF000000 + out = append(out, "PWD") + } + switch c & clockMashMask { + case clockMash1: + out = append(out, "Mash1") + case clockMash2: + out = append(out, "Mash2") + case clockMash3: + out = append(out, "Mash3") + default: + } + c &^= clockMashMask + if c&clockFlip != 0 { + out = append(out, "Flip") + c &^= clockFlip + } + if c&clockBusy != 0 { + out = append(out, "Busy") + c &^= clockBusy + } + if c&clockKill != 0 { + out = append(out, "Kill") + c &^= clockKill + } + if c&clockEnable != 0 { + out = append(out, "Enable") + c &^= clockEnable + } + switch x := c & clockSrcMask; x { + case clockSrcGND: + out = append(out, "GND(0Hz)") + case clockSrc19dot2MHz: + out = append(out, "19.2MHz") + case clockSrcTestDebug0: + out = append(out, "Debug0(0Hz)") + case clockSrcTestDebug1: + out = append(out, "Debug1(0Hz)") + case clockSrcPLLA: + out = append(out, "PLLA(0Hz)") + case clockSrcPLLC: + out = append(out, "PLLD(1000MHz)") + case clockSrcPLLD: + out = append(out, "PLLD(500MHz)") + case clockSrcHDMI: + out = append(out, "HDMI(216MHz)") + default: + out = append(out, fmt.Sprintf("GND(%d)", x)) + } + c &^= clockSrcMask + if c != 0 { + out = append(out, fmt.Sprintf("clockCtl(0x%0x)", uint32(c))) + } + return strings.Join(out, "|") +} + +const ( + // 31:24 password + clockPasswdDiv clockDiv = 0x5A << 24 // PASSWD + // Integer part of the divisor + clockDiviShift = 12 + clockDiviMax = (1 << 12) - 1 + clockDiviMask clockDiv = clockDiviMax << clockDiviShift // DIVI + // Fractional part of the divisor + clockDivfMask clockDiv = (1 << 12) - 1 // DIVF +) + +// clockDiv is a 12.12 fixed point value. +// +// The fractional part generates a significant amount of noise so it is +// preferable to not use it. +// +// Page 108 +type clockDiv uint32 + +func (c clockDiv) String() string { + i := (c & clockDiviMask) >> clockDiviShift + c &^= clockDiviMask + if c == 0 { + return fmt.Sprintf("%d.0", i) + } + return fmt.Sprintf("%d.(%d/%d)", i, c, clockDiviMax) +} + +// clock is a pair of clockCtl / clockDiv. +// +// It can be set to one of the sources: clockSrc19dot2MHz(19.2MHz) and +// clockSrcPLLD(500Mhz), then divided to a value to get the resulting clock. +// Per spec the resulting frequency should be under 25Mhz. +type clock struct { + ctl clockCtl + div clockDiv +} + +// findDivisorExact finds the clock divisor and wait cycles to reduce src to +// desired hz. +// +// The clock divisor is capped to clockDiviMax. +// +// Returns clock divisor, wait cycles. Returns 0, 0 if no exact match is found. +// Favorizes high clock divisor value over high clock wait cycles. This means +// that the function is slower than it could be, but results in more stable +// clock. +func findDivisorExact(src, desired physic.Frequency, maxWaitCycles uint32) (uint32, uint32) { + if src < desired || src%desired != 0 || src/physic.Frequency(maxWaitCycles*clockDiviMax) > desired { + // Can't attain without oversampling (too low) or desired frequency is + // higher than the source (too high) or is not a multiple. + return 0, 0 + } + factor := uint32(src / desired) + // TODO(maruel): Only iterate over valid divisors to save a bit more + // calculations. Since it's is only doing 32 loops, this is not a big deal. + for wait := uint32(1); wait <= maxWaitCycles; wait++ { + if rest := factor % wait; rest != 0 { + continue + } + clk := factor / wait + if clk == 0 { + break + } + if clk <= clockDiviMax { + return clk, wait + } + } + return 0, 0 +} + +// findDivisorOversampled tries to find the lowest allowed oversampling to make +// desiredHz a multiple of srcHz. +// +// Allowed oversampling depends on the desiredHz. Cap oversampling because +// oversampling at 10x in the 1Mhz range becomes unreasonable in term of +// memory usage. +func findDivisorOversampled(src, desired physic.Frequency, maxWaitCycles uint32) (uint32, uint32, physic.Frequency) { + //log.Printf("findDivisorOversampled(%s, %s, %d)", src, desired, maxWaitCycles) + // There are 2 reasons: + // - desired is so low it is not possible to lower src to this frequency + // - not a multiple, there's a need for a prime number + // TODO(maruel): Rewrite without a loop, this is not needed. Leverage primes + // to reduce the number of iterations. + for multiple := physic.Frequency(2); ; multiple++ { + n := multiple * desired + if n > 100*physic.KiloHertz && multiple > 10 { + break + } + if clk, wait := findDivisorExact(src, n, maxWaitCycles); clk != 0 { + return clk, wait, n + } + } + return 0, 0, 0 +} + +// calcSource choose the best source to get the exact desired clock. +// +// It calculates the clock source, the clock divisor and the wait cycles, if +// applicable. Wait cycles is 'div minus 1'. +func calcSource(f physic.Frequency, maxWaitCycles uint32) (clockCtl, uint32, uint32, physic.Frequency, error) { + if f < physic.Hertz { + return 0, 0, 0, 0, fmt.Errorf("bcm283x-clock: desired frequency %s must be >1hz", f) + } + if f > 125*physic.MegaHertz { + return 0, 0, 0, 0, fmt.Errorf("bcm283x-clock: desired frequency %s is too high", f) + } + // http://elinux.org/BCM2835_datasheet_errata states that clockSrc19dot2MHz + // is the cleanest clock source so try it first. + div, wait := findDivisorExact(clk19dot2MHz, f, maxWaitCycles) + if div != 0 { + return clockSrc19dot2MHz, div, wait, f, nil + } + // Try 500Mhz. + div, wait = findDivisorExact(clk500MHz, f, maxWaitCycles) + if div != 0 { + return clockSrcPLLD, div, wait, f, nil + } + + // Try with up to 10x oversampling. This is generally useful for lower + // frequencies, below 10kHz. Prefer the one with less oversampling. Only for + // non-aliased matches. + div19, wait19, f19 := findDivisorOversampled(clk19dot2MHz, f, maxWaitCycles) + div500, wait500, f500 := findDivisorOversampled(clk500MHz, f, maxWaitCycles) + if div19 != 0 && (div500 == 0 || f19 < f500) { + return clockSrc19dot2MHz, div19, wait19, f19, nil + } + if div500 != 0 { + return clockSrcPLLD, div500, wait500, f500, nil + } + return 0, 0, 0, 0, errors.New("failed to find a good clock") +} + +// set changes the clock frequency to the desired value or the closest one +// otherwise. +// +// f=0 means disabled. +// +// maxWaitCycles is the maximum oversampling via an additional wait cycles that +// can further divide the clock. Use 1 if no additional wait cycle is +// available. It is expected to be dmaWaitcyclesMax+1. +// +// Returns the actual clock used and divisor. +func (c *clock) set(f physic.Frequency, maxWaitCycles uint32) (physic.Frequency, uint32, error) { + if f == 0 { + c.ctl = clockPasswdCtl | clockKill + for c.ctl&clockBusy != 0 { + } + return 0, 0, nil + } + ctl, div, div2, actual, err := calcSource(f, maxWaitCycles) + if err != nil { + return 0, 0, err + } + return actual, div2, c.setRaw(ctl, div) +} + +// setRaw sets the clock speed with the clock source and the divisor. +func (c *clock) setRaw(ctl clockCtl, div uint32) error { + if div < 1 || div > clockDiviMax { + return errors.New("invalid clock divisor") + } + if ctl != clockSrc19dot2MHz && ctl != clockSrcPLLD { + return errors.New("invalid clock control") + } + // Stop the clock. + // TODO(maruel): Do not stop the clock if the current clock rate is the one + // desired. + for c.ctl&clockBusy != 0 { + c.ctl = clockPasswdCtl | clockKill + } + d := clockDiv(div << clockDiviShift) + c.div = clockPasswdDiv | d + Nanospin(10 * time.Nanosecond) + // Page 107 + c.ctl = clockPasswdCtl | ctl + Nanospin(10 * time.Nanosecond) + c.ctl = clockPasswdCtl | ctl | clockEnable + if c.div != d { + // This error is mocked out in tests, so the code path of set() callers can + // follow on. + return errClockRegister + } + return nil +} + +func (c *clock) String() string { + return fmt.Sprintf("%s / %s", c.ctl, c.div) +} + +// clockMap is the memory mapped clock registers. +// +// The clock #1 must not be touched since it is being used by the ethernet +// controller. +// +// Page 107 for gp0~gp2. +// https://scribd.com/doc/127599939/BCM2835-Audio-clocks for PCM/PWM. +type clockMap struct { + reserved0 [0x70 / 4]uint32 // + gp0 clock // CM_GP0CTL+CM_GP0DIV; 0x70-0x74 (125MHz max) + gp1ctl uint32 // CM_GP1CTL+CM_GP1DIV; 0x78-0x7A must not use (used by ethernet) + gp1div uint32 // CM_GP1CTL+CM_GP1DIV; 0x78-0x7A must not use (used by ethernet) + gp2 clock // CM_GP2CTL+CM_GP2DIV; 0x80-0x84 (125MHz max) + reserved1 [(0x98 - 0x88) / 4]uint32 // 0x88-0x94 + pcm clock // CM_PCMCTL+CM_PCMDIV 0x98-0x9C + pwm clock // CM_PWMCTL+CM_PWMDIV 0xA0-0xA4 +} + +func (c *clockMap) GoString() string { + return fmt.Sprintf( + "{\n gp0: %s,\n gp1: %s,\n gp2: %s,\n pcm: %sw,\n pwm: %s,\n}", + &c.gp0, &clock{clockCtl(c.gp1ctl), clockDiv(c.gp1div)}, &c.gp2, &c.pcm, &c.pwm) +} diff --git a/vendor/periph.io/x/periph/host/bcm283x/dma.go b/vendor/periph.io/x/periph/host/bcm283x/dma.go new file mode 100644 index 0000000..9a28003 --- /dev/null +++ b/vendor/periph.io/x/periph/host/bcm283x/dma.go @@ -0,0 +1,1224 @@ +// 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. + +// The DMA controller can be used for two functionality: +// - implement zero-CPU continuous PWM. +// - bitbang a large stream of bits over a GPIO pin, for example for WS2812b +// support. +// +// The way it works under the hood is that the bcm283x has two registers, one +// to set a bit and one to clear a bit. +// +// So two DMA controllers are used, one writing a "clear bit" stream and one +// for the "set bit" stream. This requires two independent 32 bits wide streams +// per period for write but only one for read. +// +// References +// +// Page 7: +// " Software accessing RAM directly must use physical addresses (based at +// 0x00000000). Software accessing RAM using the DMA engines must use bus +// addresses (based at 0xC0000000) " ... to skip the L1 cache. +// +// " The BCM2835 DMA Controller provides a total of 16 DMA channels. Each +// channel operates independently from the others and is internally arbitrated +// onto one of the 3 system buses. This means that the amount of bandwidth that +// a DMA channel may consume can be controlled by the arbiter settings. " +// +// The CPU has 16 DMA channels but only the first 7 (#0 to #6) can do strides. +// 7~15 have half the bandwidth. + +// +// References +// +// DMA channel allocation: +// https://github.com/raspberrypi/linux/issues/1327 +// +// DMA location: +// https://www.raspberrypi.org/forums/viewtopic.php?f=71&t=19797 + +package bcm283x + +import ( + "errors" + "fmt" + "log" + "os" + "strings" + "time" + + "periph.io/x/periph" + "periph.io/x/periph/conn/gpio/gpiostream" + "periph.io/x/periph/conn/physic" + "periph.io/x/periph/host/pmem" + "periph.io/x/periph/host/videocore" +) + +const ( + periphMask = 0x00FFFFFF + periphBus = 0x7E000000 + // maxLite is the maximum transfer allowed by a lite channel. + maxLite = 65535 +) + +// Pages 47-50 +type dmaStatus uint32 + +const ( + dmaReset dmaStatus = 1 << 31 // RESET; Writing a 1 to this bit will reset the DMA + dmaAbort dmaStatus = 1 << 30 // ABORT; Writing a 1 to this bit will abort the current DMA CB. The DMA will load the next CB and attempt to continue. + dmaDisableDebug dmaStatus = 1 << 29 // DISDEBUG; When set to 1, the DMA will not stop when the debug pause signal is asserted. + // When set to 1, the DMA will keep a tally of the AXI writes going out and + // the write responses coming in. At the very end of the current DMA transfer + // it will wait until the last outstanding write response has been received + // before indicating the transfer is complete. Whilst waiting it will load + // the next CB address (but will not fetch the CB), clear the active flag (if + // the next CB address = zero), and it will defer setting the END flag or the + // INT flag until the last outstanding write response has been received. + // In this mode, the DMA will pause if it has more than 13 outstanding writes + // at any one time. + dmaWaitForOutstandingWrites dmaStatus = 1 << 28 // WAIT_FOR_OUTSTANDING_WRITES + // 27:24 reserved + // 23:20 Lowest has higher priority on AXI. + dmaPanicPriorityShift = 20 + dmaPanicPriorityMask = 0xF << 20 // PANIC_PRIORITY + // 19:16 Lowest has higher priority on AXI. + dmaPriorityShift = 16 + dmaPriorityMask = 0xF << dmaPriorityShift // PRIORITY + // 15:9 reserved + dmaErrorStatus dmaStatus = 1 << 8 // ERROR DMA error was detected; must be cleared manually. + // 7 reserved + dmaWaitingForOutstandingWrites dmaStatus = 1 << 6 // WAITING_FOR_OUTSTANDING_WRITES; Indicates if the DMA is currently waiting for any outstanding writes to be received, and is not transferring data. + dmaDreqStopsDMA dmaStatus = 1 << 5 // DREQ_STOPS_DMA; Indicates if the DMA is currently paused and not transferring data due to the DREQ being inactive. + // Indicates if the DMA is currently paused and not transferring data. This + // will occur if: the active bit has been cleared, if the DMA is currently + // executing wait cycles or if the debug_pause signal has been set by the + // debug block, or the number of outstanding writes has exceeded the max + // count. + dmaPaused dmaStatus = 1 << 4 // PAUSED + // Indicates the state of the selected DREQ (Data Request) signal, ie. the + // DREQ selected by the PERMAP field of the transfer info. + // 1 = Requesting data. This will only be valid once the DMA has started and + // the PERMAP field has been loaded from the CB. It will remain valid, + // indicating the selected DREQ signal, until a new CB is loaded. If + // PERMAP is set to zero (unpaced transfer) then this bit will read back + // as 1. + // 0 = No data request. + dmaDreq dmaStatus = 1 << 3 // DREQ + // This is set when the transfer for the CB ends and INTEN is set to 1. Once + // set it must be manually cleared down, even if the next CB has INTEN = 0. + // Write 1 to clear. + dmaInterrupt dmaStatus = 1 << 2 // INT + // Set when the transfer described by the current control block is complete. + // Write 1 to clear. + dmaEnd dmaStatus = 1 << 1 // END + // This bit enables the DMA. The DMA will start if this bit is set and the + // CB_ADDR is non zero. The DMA transfer can be paused and resumed by + // clearing, then setting it again. + // This bit is automatically cleared at the end of the complete DMA transfer, + // ie. after a NEXTCONBK = 0x0000_0000 has been loaded. + dmaActive dmaStatus = 1 << 0 // ACTIVE +) + +var dmaStatusMap = []struct { + v dmaStatus + s string +}{ + {dmaReset, "Reset"}, + {dmaAbort, "Abort"}, + {dmaDisableDebug, "DisableDebug"}, + {dmaWaitForOutstandingWrites, "WaitForOutstandingWrites"}, + {dmaErrorStatus, "ErrorStatus"}, + {dmaWaitingForOutstandingWrites, "WaitingForOutstandingWrites"}, + {dmaDreqStopsDMA, "DreqStopsDMA"}, + {dmaPaused, "Paused"}, + {dmaDreq, "Dreq"}, + {dmaInterrupt, "Interrupt"}, + {dmaEnd, "End"}, + {dmaActive, "Active"}, +} + +func (d dmaStatus) String() string { + var out []string + for _, l := range dmaStatusMap { + if d&l.v != 0 { + d &^= l.v + out = append(out, l.s) + } + } + if v := d & dmaPanicPriorityMask; v != 0 { + out = append(out, fmt.Sprintf("pp%d", v>>dmaPanicPriorityShift)) + d &^= dmaPanicPriorityMask + } + if v := d & dmaPriorityMask; v != 0 { + out = append(out, fmt.Sprintf("p%d", v>>dmaPriorityShift)) + d &^= dmaPriorityMask + } + if d != 0 { + out = append(out, fmt.Sprintf("dmaStatus(0x%x)", uint32(d))) + } + if len(out) == 0 { + return "0" + } + return strings.Join(out, "|") +} + +// Pages 50-52 +type dmaTransferInfo uint32 + +const ( + // 31:27 reserved + // Don't do wide writes as 2 beat burst; only for channels 0 to 6 + dmaNoWideBursts dmaTransferInfo = 1 << 26 // NO_WIDE_BURSTS + // 25:21 Slows down the DMA throughput by setting the number of dummy cycles + // burnt after each DMA read or write is completed. + dmaWaitCyclesShift = 21 + dmaWaitcyclesMax = 0x1F + dmaWaitCyclesMask dmaTransferInfo = dmaWaitcyclesMax << dmaWaitCyclesShift // WAITS + // 20:16 Peripheral mapping (1-31) whose ready signal shall be used to + // control the rate of the transfers. 0 means continuous un-paced transfer. + // + // It is the source used to pace the data reads and writes operations, each + // pace being a DReq (Data Request). + // + // Page 61 + dmaPerMapShift = 16 + dmaPerMapMask dmaTransferInfo = 31 << dmaPerMapShift + dmaFire dmaTransferInfo = 0 << dmaPerMapShift // PERMAP; Continuous trigger + dmaDSI dmaTransferInfo = 1 << dmaPerMapShift // Display Serial Interface (?) + dmaPCMTX dmaTransferInfo = 2 << dmaPerMapShift // + dmaPCMRX dmaTransferInfo = 3 << dmaPerMapShift // + dmaSMI dmaTransferInfo = 4 << dmaPerMapShift // Secondary Memory Interface (?) + dmaPWM dmaTransferInfo = 5 << dmaPerMapShift // + dmaSPITX dmaTransferInfo = 6 << dmaPerMapShift // + dmaSPIRX dmaTransferInfo = 7 << dmaPerMapShift // + dmaBscSPIslaveTX dmaTransferInfo = 8 << dmaPerMapShift // + dmaBscSPIslaveRX dmaTransferInfo = 9 << dmaPerMapShift // + dmaUnused dmaTransferInfo = 10 << dmaPerMapShift // + dmaEMMC dmaTransferInfo = 11 << dmaPerMapShift // + dmaUARTTX dmaTransferInfo = 12 << dmaPerMapShift // + dmaSDHost dmaTransferInfo = 13 << dmaPerMapShift // + dmaUARTRX dmaTransferInfo = 14 << dmaPerMapShift // + dmaDSI2 dmaTransferInfo = 15 << dmaPerMapShift // Same as DSI + dmaSlimBusMCTX dmaTransferInfo = 16 << dmaPerMapShift // + dmaHDMI dmaTransferInfo = 17 << dmaPerMapShift // 216MHz; potentially a (216MHz/(26+1)) 8MHz copy rate but it fails if HDMI is disabled + dmaSlimBusMCRX dmaTransferInfo = 18 << dmaPerMapShift // + dmaSlimBusDC0 dmaTransferInfo = 19 << dmaPerMapShift // + dmaSlimBusDC1 dmaTransferInfo = 20 << dmaPerMapShift // + dmaSlimBusDC2 dmaTransferInfo = 21 << dmaPerMapShift // + dmaSlimBusDC3 dmaTransferInfo = 22 << dmaPerMapShift // + dmaSlimBusDC4 dmaTransferInfo = 23 << dmaPerMapShift // + dmaScalerFIFO0 dmaTransferInfo = 24 << dmaPerMapShift // Also on SMI; SMI can be disabled with smiDisable + dmaScalerFIFO1 dmaTransferInfo = 25 << dmaPerMapShift // + dmaScalerFIFO2 dmaTransferInfo = 26 << dmaPerMapShift // + dmaSlimBusDC5 dmaTransferInfo = 27 << dmaPerMapShift // + dmaSlimBusDC6 dmaTransferInfo = 28 << dmaPerMapShift // + dmaSlimBusDC7 dmaTransferInfo = 29 << dmaPerMapShift // + dmaSlimBusDC8 dmaTransferInfo = 30 << dmaPerMapShift // + dmaSlimBusDC9 dmaTransferInfo = 31 << dmaPerMapShift // + + dmaBurstLengthShift = 12 + dmaBurstLengthMask dmaTransferInfo = 0xF << dmaBurstLengthShift // BURST_LENGTH 15:12 0 means a single transfer. + dmaSrcIgnore dmaTransferInfo = 1 << 11 // SRC_IGNORE Source won't be read, output will be zeros. + dmaSrcDReq dmaTransferInfo = 1 << 10 // SRC_DREQ + dmaSrcWidth128 dmaTransferInfo = 1 << 9 // SRC_WIDTH 128 bits reads if set, 32 bits otherwise. + dmaSrcInc dmaTransferInfo = 1 << 8 // SRC_INC Increment read pointer by 32/128bits at each read if set. + dmaDstIgnore dmaTransferInfo = 1 << 7 // DEST_IGNORE Do not write. + dmaDstDReq dmaTransferInfo = 1 << 6 // DEST_DREQ + dmaDstWidth128 dmaTransferInfo = 1 << 5 // DEST_WIDTH 128 bits writes if set, 32 bits otherwise. + dmaDstInc dmaTransferInfo = 1 << 4 // DEST_INC Increment write pointer by 32/128bits at each read if set. + dmaWaitResp dmaTransferInfo = 1 << 3 // WAIT_RESP DMA waits for AXI write response. + // 2 reserved + // 2D mode interpret of txLen; linear if unset; only for channels 0 to 6. + dmaTransfer2DMode dmaTransferInfo = 1 << 1 // TDMODE + dmaInterruptEnable dmaTransferInfo = 1 << 0 // INTEN Generate an interrupt upon completion. +) + +var dmaTransferInfoMap = []struct { + v dmaTransferInfo + s string +}{ + {dmaNoWideBursts, "NoWideBursts"}, + {dmaSrcIgnore, "SrcIgnore"}, + {dmaSrcDReq, "SrcDReq"}, + {dmaSrcWidth128, "SrcWidth128"}, + {dmaSrcInc, "SrcInc"}, + {dmaDstIgnore, "DstIgnore"}, + {dmaDstDReq, "DstDReq"}, + {dmaDstWidth128, "DstWidth128"}, + {dmaDstInc, "DstInc"}, + {dmaWaitResp, "WaitResp"}, + {dmaTransfer2DMode, "Transfer2DMode"}, + {dmaInterruptEnable, "InterruptEnable"}, +} + +var dmaPerMap = []string{ + "Fire", + "DSI", + "PCMTX", + "PCMRX", + "SMI", + "PWM", + "SPITX", + "SPIRX", + "BscSPISlaveTX", + "BscSPISlaveRX", + "Unused", + "EMMC", + "UARTTX", + "SDHOST", + "UARTRX", + "DSI2", + "SlimBusMCTX", + "HDMI", + "SlimBusMCRX", + "SlimBusDC0", + "SlimBusDC1", + "SlimBusDC2", + "SlimBusDC3", + "SlimBusDC4", + "ScalerFIFO0", + "ScalerFIFO1", + "ScalerFIFO2", + "SlimBusDC5", + "SlimBusDC6", + "SlimBusDC7", + "SlimBusDC8", + "SlimBusDC9", +} + +func (d dmaTransferInfo) String() string { + var out []string + for _, l := range dmaTransferInfoMap { + if d&l.v != 0 { + d &^= l.v + out = append(out, l.s) + } + } + if v := d & dmaWaitCyclesMask; v != 0 { + out = append(out, fmt.Sprintf("waits=%d", v>>dmaWaitCyclesShift)) + d &^= dmaWaitCyclesMask + } + if v := d & dmaBurstLengthMask; v != 0 { + out = append(out, fmt.Sprintf("burst=%d", v>>dmaBurstLengthShift)) + d &^= dmaBurstLengthMask + } + out = append(out, dmaPerMap[(d&dmaPerMapMask)>>dmaPerMapShift]) + d &^= dmaPerMapMask + if d != 0 { + out = append(out, fmt.Sprintf("dmaTransferInfo(0x%x)", uint32(d))) + } + return strings.Join(out, "|") +} + +// Page 55 +type dmaDebug uint32 + +const ( + // 31:29 reserved + dmaLite dmaDebug = 1 << 28 // LITE RO set for lite DMA controllers + // 27:25 version + dmaVersionShift = 25 + dmaVersionMask dmaDebug = 7 << dmaVersionShift // VERSION + // 24:16 dmaState + dmaStateShift = 16 + dmaStateMask dmaDebug = 0x1FF << dmaStateShift // DMA_STATE; the actual states are not documented + // 15:8 dmaID + dmaIDShift = 8 + dmaIDMask = 0xFF << dmaIDShift // DMA_ID; the index of the DMA controller + // 7:4 outstandingWrites + dmaOutstandingWritesShift = 4 + dmaOutstandingWritesMask = 0xF << dmaOutstandingWritesShift // OUTSTANDING_WRITES + // 3 reserved + dmaReadError dmaDebug = 1 << 2 // READ_ERROR slave read error; clear by writing a 1 + dmaFIFOError dmaDebug = 1 << 1 // FIF_ERROR fifo error; clear by writing a 1 + dmaReadLastNotSetError dmaDebug = 1 << 0 // READ_LAST_NOT_SET_ERROR last AXI read signal was not set when expected +) + +var dmaDebugMap = []struct { + v dmaDebug + s string +}{ + {dmaLite, "Lite"}, + {dmaReadError, "ReadError"}, + {dmaFIFOError, "FIFOError"}, + {dmaReadLastNotSetError, "ReadLastNotSetError"}, +} + +func (d dmaDebug) String() string { + var out []string + for _, l := range dmaDebugMap { + if d&l.v != 0 { + d &^= l.v + out = append(out, l.s) + } + } + if v := d & dmaVersionMask; v != 0 { + out = append(out, fmt.Sprintf("v%d", uint32(v>>dmaVersionShift))) + d &^= dmaVersionMask + } + if v := d & dmaStateMask; v != 0 { + out = append(out, fmt.Sprintf("state(%x)", uint32(v>>dmaStateShift))) + d &^= dmaStateMask + } + if v := d & dmaIDMask; v != 0 { + out = append(out, fmt.Sprintf("#%x", uint32(v>>dmaIDShift))) + d &^= dmaIDMask + } + if v := d & dmaOutstandingWritesMask; v != 0 { + out = append(out, fmt.Sprintf("OutstandingWrites=%d", uint32(v>>dmaOutstandingWritesShift))) + d &^= dmaOutstandingWritesMask + } + if d != 0 { + out = append(out, fmt.Sprintf("dmaDebug(0x%x)", uint32(d))) + } + if len(out) == 0 { + return "0" + } + return strings.Join(out, "|") +} + +// 31:30 0 +// 29:16 yLength (only for channels #0 to #6) +// 15:0 xLength +type dmaTransferLen uint32 + +// 31:16 dstStride byte increment to apply at the end of each row in 2D mode +// 15:0 srcStride byte increment to apply at the end of each row in 2D mode +type dmaStride uint32 + +func (d dmaStride) String() string { + y := (d >> 16) & 0xFFFF + if y != 0 { + return fmt.Sprintf("0x%x,0x%x", uint32(y), uint32(d&0xFFFF)) + } + return fmt.Sprintf("0x%x", uint32(d&0xFFFF)) +} + +// controlBlock is 256 bits (32 bytes) in length. +// +// https://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf +// Page 40. +type controlBlock struct { + transferInfo dmaTransferInfo // 0x00 TI + srcAddr uint32 // 0x04 SOURCE_AD pointer to source in physical address space + dstAddr uint32 // 0x08 DEST_AD pointer to destination in physical address space + txLen dmaTransferLen // 0x0C TXFR_LEN length in bytes + stride dmaStride // 0x10 STRIDE + // Pointer to the next chained controlBlock; must be 32 bytes aligned. + // Set it to 0 to stop. + nextCB uint32 // 0x14 NEXTCONBK + reserved [2]uint32 // 0x18+0x1C +} + +// initBlock initializes a controlBlock for any valid DMA operation. +// +// l is in bytes, not in words. +// +// dreq can be dmaFire, dmaPwm, dmaPcmTx, etc. waits is additional wait state +// between clocks. +func (c *controlBlock) initBlock(srcAddr, dstAddr, l uint32, srcIO, dstIO, srcInc, dstInc bool, dreq dmaTransferInfo) error { + if srcIO && dstIO { + return errors.New("only one of src and dst can be I/O") + } + if srcAddr == 0 && dstAddr == 0 { + return errors.New("at least one source or destination is required") + } + if srcAddr == 0 && srcIO { + return errors.New("using src as I/O requires src") + } + if dstAddr == 0 && dstIO { + return errors.New("using dst as I/O requires dst") + } + if dreq&^dmaPerMapMask != 0 { + return errors.New("dreq must be one of the clock source, nothing else") + } + + t := dmaNoWideBursts | dmaWaitResp + if srcAddr == 0 { + t |= dmaSrcIgnore + c.srcAddr = 0 + } else { + if srcIO { + // Memory mapped register + c.srcAddr = physToBus(srcAddr) + } else { + // Normal memory + c.srcAddr = physToUncachedPhys(srcAddr) + } + if srcInc { + t |= dmaSrcInc + } + } + if dstAddr == 0 { + t |= dmaDstIgnore + c.dstAddr = 0 + } else { + if dstIO { + // Memory mapped register + c.dstAddr = physToBus(dstAddr) + } else { + // Normal memory + c.dstAddr = physToUncachedPhys(dstAddr) + } + if dstInc { + t |= dmaDstInc + } + } + if dreq != dmaFire { + // Inserting a wait prevents multiple transfers in a single DReq cycle. + waits := 1 + t |= dreq | dmaTransferInfo(waits<= 0; i-- { + for _, exclude := range blacklist { + if i == exclude { + goto skip + } + } + if drvDMA.dmaMemory.channels[i].isAvailable() { + drvDMA.dmaMemory.channels[i].reset() + return i, &drvDMA.dmaMemory.channels[i] + } + skip: + } + } + // Uncomment to understand the state of the DMA channels. + //log.Printf("%#v", drvDMA.dmaMemory) + return -1, nil +} + +// runIO picks a DMA channel, initialize it and runs a transfer. +// +// It tries to release the channel as soon as it can. +func runIO(pCB pmem.Mem, liteOk bool) error { + var blacklist []int + if !liteOk { + blacklist = []int{7, 8, 9, 10, 11, 12, 13, 14, 15} + } + _, ch := pickChannel(blacklist...) + if ch == nil { + return errors.New("bcm283x-dma: no channel available") + } + defer ch.reset() + ch.startIO(uint32(pCB.PhysAddr())) + return ch.wait() +} + +func allocateCB(size int) ([]controlBlock, *videocore.Mem, error) { + buf, err := drvDMA.dmaBufAllocator((size + 0xFFF) &^ 0xFFF) + if err != nil { + return nil, nil, err + } + var cb []controlBlock + if err := buf.AsPOD(&cb); err != nil { + _ = buf.Close() + return nil, nil, err + } + return cb, buf, nil +} + +// dmaWriteStreamPCM streams data to a PCM enabled pin as a half-duplex I²S +// channel. +func dmaWriteStreamPCM(p *Pin, w gpiostream.Stream) error { + d := w.Duration() + if d == 0 { + return nil + } + f := w.Frequency() + _, _, _, actualfreq, err := calcSource(f, 1) + if err != nil { + return err + } + if actualfreq != f { + return errors.New("TODO(maruel): handle oversampling") + } + + // Start clock earlier. + drvDMA.pcmMemory.reset() + _, _, err = setPCMClockSource(f) + if err != nil { + return err + } + + // Calculate the number of bytes needed. + l := (int(w.Frequency()/f) + 7) / 8 // Bytes + buf, err := drvDMA.dmaBufAllocator((l + 0xFFF) &^ 0xFFF) + if err != nil { + return err + } + defer buf.Close() + if err := copyStreamToDMABuf(w, buf.Uint32()); err != nil { + return err + } + + cb, pCB, err := allocateCB(4096) + if err != nil { + return err + } + defer pCB.Close() + reg := drvDMA.pcmBaseAddr + 0x4 // pcmMap.fifo + if err = cb[0].initBlock(uint32(buf.PhysAddr()), reg, uint32(l), false, true, true, false, dmaPCMTX); err != nil { + return err + } + + defer drvDMA.pcmMemory.reset() + // Start transfer + drvDMA.pcmMemory.set() + err = runIO(pCB, l <= maxLite) + // We have to wait PCM to be finished even after DMA finished. + for drvDMA.pcmMemory.cs&pcmTXErr == 0 { + Nanospin(10 * time.Nanosecond) + } + return err +} + +func dmaWritePWMFIFO() (*dmaChannel, *videocore.Mem, error) { + if drvDMA.dmaMemory == nil { + return nil, nil, errors.New("bcm283x-dma is not initialized; try running as root?") + } + cb, buf, err := allocateCB(32 + 4) // CB + data + if err != nil { + return nil, nil, err + } + u := buf.Uint32() + offsetBytes := uint32(32) + u[offsetBytes/4] = 0x0 + physBuf := uint32(buf.PhysAddr()) + physBit := physBuf + offsetBytes + dest := drvDMA.pwmBaseAddr + 0x18 // PWM FIFO + if err := cb[0].initBlock(physBit, dest, 4, false, true, false, false, dmaPWM); err != nil { + _ = buf.Close() + return nil, nil, err + } + cb[0].nextCB = physBuf // Loop back to self. + + _, ch := pickChannel() + if ch == nil { + _ = buf.Close() + return nil, nil, errors.New("bcm283x-dma: no channel available") + } + ch.startIO(physBuf) + + return ch, buf, nil +} + +func startPWMbyDMA(p *Pin, rng, data uint32) (*dmaChannel, *videocore.Mem, error) { + if drvDMA.dmaMemory == nil { + return nil, nil, errors.New("bcm283x-dma is not initialized; try running as root?") + } + cb, buf, err := allocateCB(2*32 + 4) // 2 CBs + mask + if err != nil { + return nil, nil, err + } + u := buf.Uint32() + cbBytes := uint32(32) + offsetBytes := cbBytes * 2 + u[offsetBytes/4] = uint32(1) << uint(p.number&31) + physBuf := uint32(buf.PhysAddr()) + physBit := physBuf + offsetBytes + dest := [2]uint32{ + drvGPIO.gpioBaseAddr + 0x28 + 4*uint32(p.number/32), // clear + drvGPIO.gpioBaseAddr + 0x1C + 4*uint32(p.number/32), // set + } + // High + if err := cb[0].initBlock(physBit, dest[1], data*4, false, true, false, false, dmaPWM); err != nil { + _ = buf.Close() + return nil, nil, err + } + cb[0].nextCB = physBuf + cbBytes + // Low + if err := cb[1].initBlock(physBit, dest[0], (rng-data)*4, false, true, false, false, dmaPWM); err != nil { + _ = buf.Close() + return nil, nil, err + } + cb[1].nextCB = physBuf // Loop back to cb[0] + + var blacklist []int + if data*4 >= 1<<16 || (rng-data)*4 >= 1<<16 { + // Don't use lite channels. + blacklist = []int{7, 8, 9, 10, 11, 12, 13, 14, 15} + } + _, ch := pickChannel(blacklist...) + + if ch == nil { + _ = buf.Close() + return nil, nil, errors.New("bcm283x-dma: no channel available") + } + ch.startIO(physBuf) + + return ch, buf, nil +} + +// overSamples calculates the skip value which are the values that are read but +// discarded as the clock is too fast. +func overSamples(s gpiostream.Stream) (int, error) { + desired := s.Frequency() + skip := drvDMA.pwmDMAFreq / desired + if skip < 1 { + return 0, fmt.Errorf("frequency is too high(%s)", desired) + } + actualFreq := drvDMA.pwmDMAFreq / skip + errorPercent := 100 * (actualFreq - desired) / desired + if errorPercent < -10 || errorPercent > 10 { + return 0, fmt.Errorf("actual resolution differs more than 10%%(%s vs %s)", desired, actualFreq) + } + return int(skip), nil +} + +// dmaReadStream streams input from a pin. +func dmaReadStream(p *Pin, b *gpiostream.BitStream) error { + skip, err := overSamples(b) + if err != nil { + return err + } + if _, err := setPWMClockSource(); err != nil { + return err + } + + // Needs 32x the memory since each read is one full uint32. On the other + // hand one could read 32 contiguous pins simultaneously at no cost. + // TODO(simokawa): Implement a function to get number of bits for all type of + // Stream + l := len(b.Bits) * 8 * 4 * int(skip) + // TODO(simokawa): Allocate multiple pages and CBs for huge buffer. + buf, err := drvDMA.dmaBufAllocator((l + 0xFFF) &^ 0xFFF) + if err != nil { + return err + } + defer buf.Close() + cb, pCB, err := allocateCB(4) + if err != nil { + return err + } + defer pCB.Close() + + reg := drvGPIO.gpioBaseAddr + 0x34 + 4*uint32(p.number/32) // GPIO Pin Level 0 + if err := cb[0].initBlock(reg, uint32(buf.PhysAddr()), uint32(l), true, false, false, true, dmaPWM); err != nil { + return err + } + err = runIO(pCB, l <= maxLite) + uint32ToBitLSBF(b.Bits, buf.Bytes(), uint8(p.number&31), skip*4) + return err +} + +// dmaWriteStreamEdges streams data to a pin as a half-duplex one controlBlock +// per bit toggle DMA stream. +// +// Memory usage is 32 bytes x number of bit changes rounded up to nearest +// 4Kb, so an arbitrary stream of 1s or 0s only takes 4Kb but a stream of +// 101010s will takes 256x the memory. +// +// TODO(maruel): Use huffman-coding-like repeated patterns detection to +// "compress" the bitstream. This trades off upfront computation for lower +// memory usage. The "compressing" function should be public, so the user can +// call it only once yet stream multiple times. +// +// TODO(maruel): Mutate the program as it goes to reduce duplication by having +// the DMA controller write in a following controlBlock.nextCB. +// handling gpiostream.Program explicitly. +func dmaWriteStreamEdges(p *Pin, w gpiostream.Stream) error { + d := w.Duration() + if d == 0 { + return nil + } + var bits []byte + var msb bool + switch v := w.(type) { + case *gpiostream.BitStream: + bits = v.Bits + msb = !v.LSBF + default: + return fmt.Errorf("unknown type: %T", v) + } + skip, err := overSamples(w) + if err != nil { + return err + } + + // Calculate the number of controlBlock needed. + count := 1 + stride := uint32(skip) + last := getBit(bits[0], 0, msb) + l := int(int64(d) * int64(w.Frequency()) / int64(physic.Hertz)) // Bits + for i := 1; i < l; i++ { + if v := getBit(bits[i/8], i%8, msb); v != last || stride == maxLite { + last = v + count++ + stride = 0 + } + stride += uint32(skip) + } + // 32 bytes for each CB and 4 bytes for the mask. + bufBytes := count*32 + 4 + cb, buf, err := allocateCB((bufBytes + 0xFFF) &^ 0xFFF) + if err != nil { + return err + } + defer buf.Close() + + // Setup the single mask buffer of 4Kb. + mask := uint32(1) << uint(p.number&31) + u := buf.Uint32() + offset := (len(buf.Bytes()) - 4) + u[offset/4] = mask + physBit := uint32(buf.PhysAddr()) + uint32(offset) + + // Other constants during the loop. + // Waits does not seem to work as expected. Not counted as DREQ pulses? + // Use PWM's rng1 instead for this. + //waits := divs - 1 + dest := [2]uint32{ + drvGPIO.gpioBaseAddr + 0x28 + 4*uint32(p.number/32), // clear + drvGPIO.gpioBaseAddr + 0x1C + 4*uint32(p.number/32), // set + } + + // Render the controlBlock's to trigger the bit trigger for either Set or + // Clear GPIO memory registers. + last = getBit(bits[0], 0, msb) + index := 0 + stride = uint32(skip) + for i := 1; i < l; i++ { + if v := getBit(bits[i/8], i%8, msb); v != last || stride == maxLite { + if err := cb[index].initBlock(physBit, dest[last], stride*4, false, true, false, false, dmaPWM); err != nil { + return err + } + // Hardcoded len(controlBlock) == 32. It is not necessary to use + // physToUncachedPhys() here. + cb[index].nextCB = uint32(buf.PhysAddr()) + uint32(32*(index+1)) + index++ + stride = 0 + last = v + } + stride += uint32(skip) + } + if err := cb[index].initBlock(physBit, dest[last], stride*4, false, true, false, false, dmaPWM); err != nil { + return err + } + + // Start clock before DMA + _, err = setPWMClockSource() + if err != nil { + return err + } + return runIO(buf, true) +} + +// dmaWriteStreamDualChannel streams data to a pin using two DMA channels. +// +// In practice this leads to a glitchy stream. +func dmaWriteStreamDualChannel(p *Pin, w gpiostream.Stream) error { + // TODO(maruel): Analyse 'w' to figure out the programs to load, and create + // the number of controlBlock needed to reduce memory usage. + // TODO(maruel): When only one channel is needed, it is much more memory + // efficient to use DMA to write to PWM FIFO. + skip, err := overSamples(w) + if err != nil { + return err + } + // Calculates the number of needed bytes. + l := int(int64(w.Duration())*int64(w.Frequency())/int64(physic.Hertz)) * skip * 4 + bufLen := (l + 0xFFF) &^ 0xFFF + bufSet, err := drvDMA.dmaBufAllocator(bufLen) + if err != nil { + return err + } + defer bufSet.Close() + bufClear, err := drvDMA.dmaBufAllocator(bufLen) + if err != nil { + return err + } + defer bufClear.Close() + cb, pCB, err := allocateCB(4096) + if err != nil { + return err + } + defer pCB.Close() + + // Needs 64x the memory since each write is 2 full uint32. On the other + // hand one could write 32 contiguous pins simultaneously at no cost. + mask := uint32(1) << uint(p.number&31) + if err := raster32(w, skip, bufClear.Uint32(), bufSet.Uint32(), mask); err != nil { + return err + } + + // Start clock before DMA start + _, err = setPWMClockSource() + if err != nil { + return err + } + + regSet := drvGPIO.gpioBaseAddr + 0x1C + 4*uint32(p.number/32) + if err := cb[0].initBlock(uint32(bufSet.PhysAddr()), regSet, uint32(l), false, true, true, false, dmaPWM); err != nil { + return err + } + regClear := drvGPIO.gpioBaseAddr + 0x28 + 4*uint32(p.number/32) + if err := cb[1].initBlock(uint32(bufClear.PhysAddr()), regClear, uint32(l), false, true, true, false, dmaPWM); err != nil { + return err + } + + // The first channel must be a full bandwidth one. The "light" ones are + // effectively a single one, which means that they are interleaved. If both + // are "light" then the jitter is largely increased. + x, chSet := pickChannel(6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + if chSet == nil { + return errors.New("bcm283x-dma: no channel available") + } + defer chSet.reset() + _, chClear := pickChannel(x) + if chClear == nil { + return errors.New("bcm283x-dma: no secondary channel available") + } + defer chClear.reset() + + // Two channel need to be synchronized but there is not such a mechanism. + chSet.startIO(uint32(pCB.PhysAddr())) // cb[0] + chClear.startIO(uint32(pCB.PhysAddr()) + 32) // cb[1] + + err1 := chSet.wait() + err2 := chClear.wait() + if err1 == nil { + return err2 + } + return err1 +} + +// physToUncachedPhys returns the uncached physical memory address backing a +// physical memory address. +// +// p must be rooted at a page boundary (4096). +func physToUncachedPhys(p uint32) uint32 { + // http://en.wikibooks.org/wiki/Aros/Platforms/Arm_Raspberry_Pi_support#Framebuffer + return p | drvGPIO.dramBus +} + +func physToBus(p uint32) uint32 { + return (p & periphMask) | periphBus +} + +// smokeTest allocates two physical pages, ask the DMA controller to copy the +// data from one page to another and make sure the content is as expected. +// +// This should take a fraction of a second and will make sure the driver is +// usable. This ensures there's at least one DMA channel available. +func smokeTest() error { + // If these are commented out due to a new processor having different + // characteristics, the corresponding code needs to be updated. + if drvDMA.dmaMemory.channels[6].debug&dmaLite != 0 { + return errors.New("unexpected hardware: DMA channel #6 shouldn't be lite") + } + if drvDMA.dmaMemory.channels[7].debug&dmaLite == 0 { + return errors.New("unexpected hardware: DMA channel #7 should be lite") + } + if drvDMA.dmaMemory.enable != 0x7FFF { + return errors.New("unexpected hardware: DMA enable is not fully set") + } + + const size = 4096 * 4 // 16kb + const holeSize = 1 // Minimum DMA alignment + + alloc := func(s int) (pmem.Mem, error) { + return videocore.Alloc(s) + } + + copyMem := func(pDst, pSrc uint64) error { + // Allocate a control block and initialize it. + pCB, err2 := videocore.Alloc(4096) + if err2 != nil { + return err2 + } + defer pCB.Close() + var cb *controlBlock + if err := pCB.AsPOD(&cb); err != nil { + return err + } + if false { + // This code is not run by default because it resets the PWM clock on + // process startup, which may cause undesirable glitches. + + // Initializes the PWM clock right away to 1MHz. + _, err := setPWMClockSource() + if err != nil { + return err + } + if err := cb.initBlock(uint32(pSrc), uint32(pDst)+holeSize, size-2*holeSize, false, false, true, true, dmaPWM); err != nil { + return err + } + } else { + // Use maximum performance. + if err := cb.initBlock(uint32(pSrc), uint32(pDst)+holeSize, size-2*holeSize, false, false, true, true, dmaFire); err != nil { + return err + } + } + return runIO(pCB, size-2*holeSize <= maxLite) + } + + return pmem.TestCopy(size, holeSize, alloc, copyMem) +} + +// driverDMA implements periph.Driver. +// +// It implements much more than the DMA controller, it also exposes the clocks, +// the PWM and PCM controllers. +type driverDMA struct { + pcmBaseAddr uint32 + pwmBaseAddr uint32 + + dmaMemory *dmaMap + dmaChannel15 *dmaChannel + pcmMemory *pcmMap + clockMemory *clockMap + timerMemory *timerMap + gpioPadMemory *gpioPadMap + // Page 138 + // - Two independent bit-streams + // - Each channel either a PWM or serialised version of a 32-bit word + // - Variable input and output resolutions. + // - Load data from a FIFO storage block, to extent to 8 32-bit words (256 + // bits). + // + // Author note: 100Mhz base resolution with a 256 bits 1-bit stream is + // actually good enough to generate a DAC. + pwmMemory *pwmMap + + // These clocks are shared with hardware PWM, DMA driven PWM and BitStream. + pwmBaseFreq physic.Frequency + pwmDMAFreq physic.Frequency + pwmDMACh *dmaChannel + pwmDMABuf *videocore.Mem + + // dmaBufAllocator is overridden for unit testing. + dmaBufAllocator func(s int) (*videocore.Mem, error) // Set to videocore.Alloc +} + +func (d *driverDMA) Close() error { + // TODO(maruel): Stop DMA and PWM controllers. + d.pcmBaseAddr = 0 + d.pwmBaseAddr = 0 + d.dmaMemory = nil + d.dmaChannel15 = nil + d.pcmMemory = nil + d.clockMemory = nil + d.timerMemory = nil + d.pwmMemory = nil + d.pwmBaseFreq = 0 + d.pwmDMAFreq = 0 + d.pwmDMACh = nil + d.pwmDMABuf = nil + d.dmaBufAllocator = nil + return nil +} + +func (d *driverDMA) String() string { + return "bcm283x-dma" +} + +func (d *driverDMA) Prerequisites() []string { + return []string{"bcm283x-gpio"} +} + +func (d *driverDMA) After() []string { + return nil +} + +func (d *driverDMA) Init() (bool, error) { + d.dmaBufAllocator = videocore.Alloc + d.pwmBaseFreq = 25 * physic.MegaHertz + d.pwmDMAFreq = 200 * physic.KiloHertz + // baseAddr is initialized by prerequisite driver bcm283x-gpio. + if err := pmem.MapAsPOD(uint64(drvGPIO.baseAddr+0x7000), &d.dmaMemory); err != nil { + if os.IsPermission(err) { + return true, fmt.Errorf("need more access, try as root: %v", err) + } + return true, err + } + // Channel #15 is "physically removed from the other DMA Channels so it has a + // different address base". + if err := pmem.MapAsPOD(uint64(drvGPIO.baseAddr+0xE05000), &d.dmaChannel15); err != nil { + return true, err + } + d.pcmBaseAddr = drvGPIO.baseAddr + 0x203000 + if err := pmem.MapAsPOD(uint64(d.pcmBaseAddr), &d.pcmMemory); err != nil { + return true, err + } + d.pwmBaseAddr = drvGPIO.baseAddr + 0x20C000 + if err := pmem.MapAsPOD(uint64(d.pwmBaseAddr), &d.pwmMemory); err != nil { + return true, err + } + if err := pmem.MapAsPOD(uint64(drvGPIO.baseAddr+0x101000), &d.clockMemory); err != nil { + return true, err + } + if err := pmem.MapAsPOD(uint64(drvGPIO.baseAddr+0x3000), &d.timerMemory); err != nil { + return true, err + } + if err := pmem.MapAsPOD(uint64(drvGPIO.baseAddr+0x100000), &d.gpioPadMemory); err != nil { + return true, err + } + // Do not run smokeTest() unless it's clear it is not dangerous. + return true, nil +} + +func debugDMA() { + for i, ch := range drvDMA.dmaMemory.channels { + log.Println(i, ch.cs.String()) + if ch.cs&dmaActive != 0 { + log.Printf("%x: %s", ch.cbAddr, ch.GoString()) + } + } + log.Println(15, drvDMA.dmaChannel15.cs.String()) +} + +func resetDMA(ch int) error { + if ch < len(drvDMA.dmaMemory.channels) { + drvDMA.dmaMemory.channels[ch].reset() + } else if ch == 15 { + drvDMA.dmaChannel15.reset() + } else { + return fmt.Errorf("invalid dma channel %d", ch) + } + return nil +} + +func init() { + if isArm { + periph.MustRegister(&drvDMA) + } +} + +var drvDMA driverDMA diff --git a/vendor/periph.io/x/periph/host/bcm283x/doc.go b/vendor/periph.io/x/periph/host/bcm283x/doc.go new file mode 100644 index 0000000..d18ea29 --- /dev/null +++ b/vendor/periph.io/x/periph/host/bcm283x/doc.go @@ -0,0 +1,42 @@ +// Copyright 2016 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 bcm283x exposes the BCM283x GPIO functionality. +// +// This driver implements memory-mapped GPIO pin manipulation and leverages +// sysfs-gpio for edge detection. +// +// If you are looking for the actual implementation, open doc.go for further +// implementation details. +// +// GPIOs +// +// Aliases for GPCLK0, GPCLK1, GPCLK2 are created for corresponding CLKn pins. +// Same for PWM0_OUT and PWM1_OUT, which point respectively to PWM0 and PWM1. +// +// Datasheet +// +// https://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf +// +// Its crowd-sourced errata: http://elinux.org/BCM2835_datasheet_errata +// +// BCM2836: +// https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf +// +// Another doc about PCM and PWM: +// https://scribd.com/doc/127599939/BCM2835-Audio-clocks +// +// GPIO pad control: +// https://scribd.com/doc/101830961/GPIO-Pads-Control2 +package bcm283x + +// Other implementations details +// +// mainline: +// https://github.com/torvalds/linux/blob/master/drivers/dma/bcm2835-dma.c +// https://github.com/torvalds/linux/blob/master/drivers/gpio +// +// Raspbian kernel: +// https://github.com/raspberrypi/linux/blob/rpi-4.11.y/drivers/dma +// https://github.com/raspberrypi/linux/blob/rpi-4.11.y/drivers/gpio diff --git a/vendor/periph.io/x/periph/host/bcm283x/gpio.go b/vendor/periph.io/x/periph/host/bcm283x/gpio.go new file mode 100644 index 0000000..2231209 --- /dev/null +++ b/vendor/periph.io/x/periph/host/bcm283x/gpio.go @@ -0,0 +1,1361 @@ +// 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 bcm283x + +import ( + "errors" + "fmt" + "os" + "strconv" + "strings" + "time" + + "periph.io/x/periph" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/gpio/gpioreg" + "periph.io/x/periph/conn/gpio/gpiostream" + "periph.io/x/periph/conn/physic" + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/host/distro" + "periph.io/x/periph/host/pmem" + "periph.io/x/periph/host/sysfs" + "periph.io/x/periph/host/videocore" +) + +// All the pins supported by the CPU. +var ( + GPIO0 *Pin // I2C0_SDA + GPIO1 *Pin // I2C0_SCL + GPIO2 *Pin // I2C1_SDA + GPIO3 *Pin // I2C1_SCL + GPIO4 *Pin // CLK0 + GPIO5 *Pin // CLK1 + GPIO6 *Pin // CLK2 + GPIO7 *Pin // SPI0_CS1 + GPIO8 *Pin // SPI0_CS0 + GPIO9 *Pin // SPI0_MISO + GPIO10 *Pin // SPI0_MOSI + GPIO11 *Pin // SPI0_CLK + GPIO12 *Pin // PWM0 + GPIO13 *Pin // PWM1 + GPIO14 *Pin // UART0_TX, UART1_TX + GPIO15 *Pin // UART0_RX, UART1_RX + GPIO16 *Pin // UART0_CTS, SPI1_CS2, UART1_CTS + GPIO17 *Pin // UART0_RTS, SPI1_CS1, UART1_RTS + GPIO18 *Pin // I2S_SCK, SPI1_CS0, PWM0 + GPIO19 *Pin // I2S_WS, SPI1_MISO, PWM1 + GPIO20 *Pin // I2S_DIN, SPI1_MOSI, CLK0 + GPIO21 *Pin // I2S_DOUT, SPI1_CLK, CLK1 + GPIO22 *Pin // + GPIO23 *Pin // + GPIO24 *Pin // + GPIO25 *Pin // + GPIO26 *Pin // + GPIO27 *Pin // + GPIO28 *Pin // I2C0_SDA, I2S_SCK + GPIO29 *Pin // I2C0_SCL, I2S_WS + GPIO30 *Pin // I2S_DIN, UART0_CTS, UART1_CTS + GPIO31 *Pin // I2S_DOUT, UART0_RTS, UART1_RTS + GPIO32 *Pin // CLK0, UART0_TX, UART1_TX + GPIO33 *Pin // UART0_RX, UART1_RX + GPIO34 *Pin // CLK0 + GPIO35 *Pin // SPI0_CS1 + GPIO36 *Pin // SPI0_CS0, UART0_TX + GPIO37 *Pin // SPI0_MISO, UART0_RX + GPIO38 *Pin // SPI0_MOSI, UART0_RTS + GPIO39 *Pin // SPI0_CLK, UART0_CTS + GPIO40 *Pin // PWM0, SPI2_MISO, UART1_TX + GPIO41 *Pin // PWM1, SPI2_MOSI, UART1_RX + GPIO42 *Pin // CLK1, SPI2_CLK, UART1_RTS + GPIO43 *Pin // CLK2, SPI2_CS0, UART1_CTS + GPIO44 *Pin // CLK1, I2C0_SDA, I2C1_SDA, SPI2_CS1 + GPIO45 *Pin // PWM1, I2C0_SCL, I2C1_SCL, SPI2_CS2 + GPIO46 *Pin // + // Pins 47~53 are not exposed because using them would lead to immediate SD + // Card corruption. +) + +// Present returns true if running on a Broadcom bcm283x based CPU. +func Present() bool { + if isArm { + hardware, ok := distro.CPUInfo()["Hardware"] + return ok && strings.HasPrefix(hardware, "BCM") + } + return false +} + +// PinsRead0To31 returns the value of all GPIO0 to GPIO31 at their corresponding +// bit as a single read operation. +// +// This function is extremely fast and does no error checking. +// +// The returned bits are valid for both inputs and outputs. +func PinsRead0To31() uint32 { + return drvGPIO.gpioMemory.level[0] +} + +// PinsClear0To31 clears the value of GPIO0 to GPIO31 pin for the bit set at +// their corresponding bit as a single write operation. +// +// This function is extremely fast and does no error checking. +func PinsClear0To31(mask uint32) { + drvGPIO.gpioMemory.outputClear[0] = mask +} + +// PinsSet0To31 sets the value of GPIO0 to GPIO31 pin for the bit set at their +// corresponding bit as a single write operation. +func PinsSet0To31(mask uint32) { + drvGPIO.gpioMemory.outputSet[0] = mask +} + +// PinsRead32To46 returns the value of all GPIO32 to GPIO46 at their +// corresponding 'bit minus 32' as a single read operation. +// +// This function is extremely fast and does no error checking. +// +// The returned bits are valid for both inputs and outputs. +// +// Bits above 15 are guaranteed to be 0. +// +// This function is not recommended on Raspberry Pis as these GPIOs are not +// easily accessible. +func PinsRead32To46() uint32 { + return drvGPIO.gpioMemory.level[1] & 0x7fff +} + +// PinsClear32To46 clears the value of GPIO31 to GPIO46 pin for the bit set at +// their corresponding 'bit minus 32' as a single write operation. +// +// This function is extremely fast and does no error checking. +// +// Bits above 15 are ignored. +// +// This function is not recommended on Raspberry Pis as these GPIOs are not +// easily accessible. +func PinsClear32To46(mask uint32) { + drvGPIO.gpioMemory.outputClear[1] = (mask & 0x7fff) +} + +// PinsSet32To46 sets the value of GPIO31 to GPIO46 pin for the bit set at +// their corresponding 'bit minus 32' as a single write operation. +// +// This function is extremely fast and does no error checking. +// +// Bits above 15 are ignored. +// +// This function is not recommended on Raspberry Pis as these GPIOs are not +// easily accessible. +func PinsSet32To46(mask uint32) { + drvGPIO.gpioMemory.outputSet[1] = (mask & 0x7fff) +} + +// PinsSetup0To27 sets the output current drive strength, output slew limiting +// and input hysteresis for GPIO 0 to 27. +// +// Default drive is 8mA, slew unlimited and hysteresis enabled. +// +// Can only be used if driver bcm283x-dma was loaded. +func PinsSetup0To27(drive physic.ElectricCurrent, slewLimit, hysteresis bool) error { + if drvDMA.gpioPadMemory == nil { + return errors.New("bcm283x-dma not initialized; try again as root?") + } + drvDMA.gpioPadMemory.pads0.set(toPad(drive, slewLimit, hysteresis)) + return nil +} + +// PinsSetup28To45 sets the output current drive strength, output slew limiting +// and input hysteresis for GPIO 28 to 45. +// +// Default drive is 16mA, slew unlimited and hysteresis enabled. +// +// Can only be used if driver bcm283x-dma was loaded. +// +// This function is not recommended on Raspberry Pis as these GPIOs are not +// easily accessible. +func PinsSetup28To45(drive physic.ElectricCurrent, slewLimit, hysteresis bool) error { + if drvDMA.gpioPadMemory == nil { + return errors.New("bcm283x-dma not initialized; try again as root?") + } + drvDMA.gpioPadMemory.pads1.set(toPad(drive, slewLimit, hysteresis)) + return nil +} + +// Pin is a GPIO number (GPIOnn) on BCM238(5|6|7). +// +// Pin implements gpio.PinIO. +type Pin struct { + // Immutable. + number int + name string + defaultPull gpio.Pull // Default pull at system boot, as per datasheet. + + // Immutable after driver initialization. + sysfsPin *sysfs.Pin // Set to the corresponding sysfs.Pin, if any. + + // Mutable. + usingEdge bool // Set when edge detection is enabled. + usingClock bool // Set when a CLK, PWM or I2S/PCM clock is used. + dmaCh *dmaChannel // Set when DMA is used for PWM or I2S/PCM. + dmaBuf *videocore.Mem // Set when DMA is used for PWM or I2S/PCM. +} + +// String implements conn.Resource. +func (p *Pin) String() string { + return p.name +} + +// Halt implements conn.Resource. +// +// If the pin is running a clock, PWM or waiting for edges, it is halted. +// +// In the case of clock or PWM, all pins with this clock source are also +// disabled. +func (p *Pin) Halt() error { + if p.usingEdge { + if err := p.sysfsPin.Halt(); err != nil { + return p.wrap(err) + } + p.usingEdge = false + } + return p.haltClock() +} + +// Name implements pin.Pin. +func (p *Pin) Name() string { + return p.name +} + +// Number implements pin.Pin. +// +// This is the GPIO number, not the pin number on a header. +func (p *Pin) Number() int { + return p.number +} + +// Function implements pin.Pin. +func (p *Pin) Function() string { + return string(p.Func()) +} + +// Func implements pin.PinFunc. +func (p *Pin) Func() pin.Func { + if drvGPIO.gpioMemory == nil { + if p.sysfsPin == nil { + return pin.FuncNone + } + return p.sysfsPin.Func() + } + switch f := p.function(); f { + case in: + if p.FastRead() { + return gpio.IN_HIGH + } + return gpio.IN_LOW + case out: + if p.FastRead() { + return gpio.OUT_HIGH + } + return gpio.OUT_LOW + case alt0: + if s := mapping[p.number][0]; len(s) != 0 { + return s + } + return pin.Func("ALT0") + case alt1: + if s := mapping[p.number][1]; len(s) != 0 { + return s + } + return pin.Func("ALT1") + case alt2: + if s := mapping[p.number][2]; len(s) != 0 { + return s + } + return pin.Func("ALT2") + case alt3: + if s := mapping[p.number][3]; len(s) != 0 { + return s + } + return pin.Func("ALT3") + case alt4: + if s := mapping[p.number][4]; len(s) != 0 { + return s + } + return pin.Func("ALT4") + case alt5: + if s := mapping[p.number][5]; len(s) != 0 { + return s + } + return pin.Func("ALT5") + default: + return pin.FuncNone + } +} + +// SupportedFuncs implements pin.PinFunc. +func (p *Pin) SupportedFuncs() []pin.Func { + f := make([]pin.Func, 0, 2+4) + f = append(f, gpio.IN, gpio.OUT) + for _, m := range mapping[p.number] { + if m != "" { + f = append(f, m) + } + } + return f +} + +// SetFunc implements pin.PinFunc. +func (p *Pin) SetFunc(f pin.Func) error { + if drvGPIO.gpioMemory == nil { + if p.sysfsPin == nil { + return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible")) + } + return p.sysfsPin.SetFunc(f) + } + switch f { + case gpio.FLOAT: + return p.In(gpio.Float, gpio.NoEdge) + case gpio.IN: + return p.In(gpio.PullNoChange, gpio.NoEdge) + case gpio.IN_LOW: + return p.In(gpio.PullDown, gpio.NoEdge) + case gpio.IN_HIGH: + return p.In(gpio.PullUp, gpio.NoEdge) + case gpio.OUT_HIGH: + return p.Out(gpio.High) + case gpio.OUT_LOW: + return p.Out(gpio.Low) + default: + isGeneral := f == f.Generalize() + for i, m := range mapping[p.number] { + if m == f || (isGeneral && m.Generalize() == f) { + if err := p.Halt(); err != nil { + return err + } + switch i { + case 0: + p.setFunction(alt0) + case 1: + p.setFunction(alt1) + case 2: + p.setFunction(alt2) + case 3: + p.setFunction(alt3) + case 4: + p.setFunction(alt4) + case 5: + p.setFunction(alt5) + } + return nil + } + } + return p.wrap(errors.New("unsupported function")) + } +} + +// In implements gpio.PinIn. +// +// Specifying a value for pull other than gpio.PullNoChange causes this +// function to be slightly slower (about 1ms). +// +// For pull down, the resistor is 50KOhm~60kOhm +// For pull up, the resistor is 50kOhm~65kOhm +// +// The pull resistor stays set even after the processor shuts down. It is not +// possible to 'read back' what value was specified for each pin. +// +// Will fail if requesting to change a pin that is set to special functionality. +// +// Using edge detection requires opening a gpio sysfs file handle. On Raspbian, +// make sure the user is member of group 'gpio'. The pin will be exported at +// /sys/class/gpio/gpio*/. Note that the pin will not be unexported at +// shutdown. +// +// For edge detection, the processor samples the input at its CPU clock rate +// and looks for '011' to rising and '100' for falling detection to avoid +// glitches. Because gpio sysfs is used, the latency is unpredictable. +func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error { + if p.usingEdge && edge == gpio.NoEdge { + if err := p.sysfsPin.Halt(); err != nil { + return p.wrap(err) + } + } + if drvGPIO.gpioMemory == nil { + if p.sysfsPin == nil { + return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible")) + } + if pull != gpio.PullNoChange { + return p.wrap(errors.New("pull cannot be used when subsystem gpiomem not initialized")) + } + if err := p.sysfsPin.In(pull, edge); err != nil { + return p.wrap(err) + } + p.usingEdge = edge != gpio.NoEdge + return nil + } + if err := p.haltClock(); err != nil { + return err + } + p.setFunction(in) + if pull != gpio.PullNoChange { + // Changing pull resistor requires a specific dance as described at + // https://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf + // page 101. + + // Set Pull + switch pull { + case gpio.PullDown: + drvGPIO.gpioMemory.pullEnable = 1 + case gpio.PullUp: + drvGPIO.gpioMemory.pullEnable = 2 + case gpio.Float: + drvGPIO.gpioMemory.pullEnable = 0 + } + + // Datasheet states caller needs to sleep 150 cycles. + sleep150cycles() + offset := p.number / 32 + drvGPIO.gpioMemory.pullEnableClock[offset] = 1 << uint(p.number%32) + + sleep150cycles() + drvGPIO.gpioMemory.pullEnable = 0 + drvGPIO.gpioMemory.pullEnableClock[offset] = 0 + } + if edge != gpio.NoEdge { + if p.sysfsPin == nil { + return p.wrap(fmt.Errorf("pin %d is not exported by sysfs", p.number)) + } + // This resets pending edges. + if err := p.sysfsPin.In(gpio.PullNoChange, edge); err != nil { + return p.wrap(err) + } + p.usingEdge = true + } + return nil +} + +// Read implements gpio.PinIn. +// +// This function is fast. It works even if the pin is set as output. +func (p *Pin) Read() gpio.Level { + if drvGPIO.gpioMemory == nil { + if p.sysfsPin == nil { + return gpio.Low + } + return p.sysfsPin.Read() + } + if p.number < 32 { + // Important: do not remove the &31 here even if not necessary. Testing + // showed that it slows down the performance by several percents. + return gpio.Level((drvGPIO.gpioMemory.level[0] & (1 << uint(p.number&31))) != 0) + } + return gpio.Level((drvGPIO.gpioMemory.level[1] & (1 << uint(p.number&31))) != 0) +} + +// FastRead return the current pin level without any error checking. +// +// This function is very fast. It works even if the pin is set as output. +func (p *Pin) FastRead() gpio.Level { + if p.number < 32 { + // Important: do not remove the &31 here even if not necessary. Testing + // showed that it slows down the performance by several percents. + return gpio.Level((drvGPIO.gpioMemory.level[0] & (1 << uint(p.number&31))) != 0) + } + return gpio.Level((drvGPIO.gpioMemory.level[1] & (1 << uint(p.number&31))) != 0) +} + +// WaitForEdge implements gpio.PinIn. +func (p *Pin) WaitForEdge(timeout time.Duration) bool { + if p.sysfsPin != nil { + return p.sysfsPin.WaitForEdge(timeout) + } + return false +} + +// Pull implements gpio.PinIn. +// +// bcm283x doesn't support querying the pull resistor of any GPIO pin. +func (p *Pin) Pull() gpio.Pull { + // TODO(maruel): The best that could be added is to cache the last set value + // and return it. + return gpio.PullNoChange +} + +// DefaultPull implements gpio.PinIn. +// +// The CPU doesn't return the current pull. +func (p *Pin) DefaultPull() gpio.Pull { + return p.defaultPull +} + +// Out implements gpio.PinOut. +// +// Fails if requesting to change a pin that is set to special functionality. +func (p *Pin) Out(l gpio.Level) error { + if drvGPIO.gpioMemory == nil { + if p.sysfsPin == nil { + return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible")) + } + return p.sysfsPin.Out(l) + } + // TODO(maruel): This function call is very costly. + if err := p.Halt(); err != nil { + return err + } + // Change output before changing mode to not create any glitch. + p.FastOut(l) + p.setFunction(out) + return nil +} + +// FastOut sets a pin output level with Absolutely No error checking. +// +// Out() Must be called once first before calling FastOut(), otherwise the +// behavior is undefined. Then FastOut() can be used for minimal CPU overhead +// to reach Mhz scale bit banging. +func (p *Pin) FastOut(l gpio.Level) { + mask := uint32(1) << uint(p.number&31) + if l == gpio.Low { + if p.number < 32 { + drvGPIO.gpioMemory.outputClear[0] = mask + } else { + drvGPIO.gpioMemory.outputClear[1] = mask + } + } else { + if p.number < 32 { + drvGPIO.gpioMemory.outputSet[0] = mask + } else { + drvGPIO.gpioMemory.outputSet[1] = mask + } + } +} + +// BUG(maruel): PWM(): There is no conflict verification when multiple pins are +// used simultaneously. The last call to PWM() will affect all pins of the same +// type (CLK0, CLK2, PWM0 or PWM1). + +// PWM implements gpio.PinOut. +// +// It outputs a periodic signal on supported pins without CPU usage. +// +// PWM pins +// +// PWM0 is exposed on pins 12, 18 and 40. However, PWM0 is used for generating +// clock for DMA and unavailable for PWM. +// +// PWM1 is exposed on pins 13, 19, 41 and 45. +// +// PWM1 uses 25Mhz clock source. The frequency must be a divisor of 25Mhz. +// +// DMA driven PWM is available for all pins except PWM1 pins, its resolution is +// 200KHz which is down-sampled from 25MHz clock above. The number of DMA driven +// PWM is limited. +// +// Furthermore, these can only be used if the drive "bcm283x-dma" was loaded. +// It can only be loaded if the process has root level access. +// +// The user must call either Halt(), In(), Out(), PWM(0,..) or +// PWM(gpio.DutyMax,..) to stop the clock source and DMA engine before exiting +// the program. +func (p *Pin) PWM(duty gpio.Duty, freq physic.Frequency) error { + if duty == 0 { + return p.Out(gpio.Low) + } else if duty == gpio.DutyMax { + return p.Out(gpio.High) + } + f := out + useDMA := false + switch p.number { + case 12, 40: // PWM0 alt0: disabled + useDMA = true + case 13, 41, 45: // PWM1 + f = alt0 + case 18: // PWM0 alt5: disabled + useDMA = true + case 19: // PWM1 + f = alt5 + default: + useDMA = true + } + + // Intentionally check later, so a more informative error is returned on + // unsupported pins. + if drvGPIO.gpioMemory == nil { + return p.wrap(errors.New("subsystem gpiomem not initialized")) + } + if drvDMA.pwmMemory == nil || drvDMA.clockMemory == nil { + return p.wrap(errors.New("bcm283x-dma not initialized; try again as root?")) + } + if useDMA { + if m := drvDMA.pwmDMAFreq / 2; m < freq { + return p.wrap(fmt.Errorf("frequency must be at most %s", m)) + } + + // Total cycles in the period + rng := uint64(drvDMA.pwmDMAFreq / freq) + // Pulse width cycles + dat := uint32((rng*uint64(duty) + uint64(gpio.DutyHalf)) / uint64(gpio.DutyMax)) + var err error + // TODO(simokawa): Reuse DMA buffer if possible. + if err = p.haltDMA(); err != nil { + return p.wrap(err) + } + // Start clock before DMA starts. + if _, err = setPWMClockSource(); err != nil { + return p.wrap(err) + } + if p.dmaCh, p.dmaBuf, err = startPWMbyDMA(p, uint32(rng), dat); err != nil { + return p.wrap(err) + } + } else { + if m := drvDMA.pwmBaseFreq / 2; m < freq { + return p.wrap(fmt.Errorf("frequency must be at most %s", m)) + } + // Total cycles in the period + rng := uint64(drvDMA.pwmBaseFreq / freq) + // Pulse width cycles + dat := uint32((rng*uint64(duty) + uint64(gpio.DutyHalf)) / uint64(gpio.DutyMax)) + if _, err := setPWMClockSource(); err != nil { + return p.wrap(err) + } + // Bit shift for PWM0 and PWM1 + shift := uint((p.number & 1) * 8) + if shift == 0 { + drvDMA.pwmMemory.rng1 = uint32(rng) + Nanospin(10 * time.Nanosecond) + drvDMA.pwmMemory.dat1 = uint32(dat) + } else { + drvDMA.pwmMemory.rng2 = uint32(rng) + Nanospin(10 * time.Nanosecond) + drvDMA.pwmMemory.dat2 = uint32(dat) + } + Nanospin(10 * time.Nanosecond) + old := drvDMA.pwmMemory.ctl + drvDMA.pwmMemory.ctl = (old & ^(0xff << shift)) | ((pwm1Enable | pwm1MS) << shift) + } + p.usingClock = true + p.setFunction(f) + return nil +} + +// StreamIn implements gpiostream.PinIn. +// +// DMA driven StreamOut is available for GPIO0 to GPIO31 pin and the maximum +// resolution is 200kHz. +func (p *Pin) StreamIn(pull gpio.Pull, s gpiostream.Stream) error { + b, ok := s.(*gpiostream.BitStream) + if !ok { + return errors.New("bcm283x: other Stream than BitStream are not implemented yet") + } + if !b.LSBF { + return errors.New("bcm283x: MSBF BitStream is not implemented yet") + } + if b.Duration() == 0 { + return errors.New("bcm283x: can't read to empty BitStream") + } + if drvGPIO.gpioMemory == nil { + return p.wrap(errors.New("subsystem gpiomem not initialized")) + } + if err := p.In(pull, gpio.NoEdge); err != nil { + return err + } + if err := dmaReadStream(p, b); err != nil { + return p.wrap(err) + } + return nil +} + +// StreamOut implements gpiostream.PinOut. +// +// I2S/PCM driven StreamOut is available for GPIO21 pin. The resolution is up to +// 250MHz. +// +// For GPIO0 to GPIO31 except GPIO21 pin, DMA driven StreamOut is available and +// the maximum resolution is 200kHz. +func (p *Pin) StreamOut(s gpiostream.Stream) error { + if drvGPIO.gpioMemory == nil { + return p.wrap(errors.New("subsystem gpiomem not initialized")) + } + if err := p.Out(gpio.Low); err != nil { + return err + } + // If the pin is I2S_DOUT, use PCM for much nicer stream and lower memory + // usage. + if p.number == 21 || p.number == 31 { + alt := alt0 + if p.number == 31 { + alt = alt2 + } + p.setFunction(alt) + if err := dmaWriteStreamPCM(p, s); err != nil { + return p.wrap(err) + } + } else if err := dmaWriteStreamEdges(p, s); err != nil { + return p.wrap(err) + } + return nil +} + +// Drive returns the configured output current drive strength for this GPIO. +// +// The current drive is configurable per GPIO groups: 0~27 and 28~45. +// +// The default value for GPIOs 0~27 is 8mA and for GPIOs 28~45 is 16mA. +// +// The value is a multiple 2mA between 2mA and 16mA. +// +// Can only be used if driver bcm283x-dma was loaded. Otherwise returns 0. +func (p *Pin) Drive() physic.ElectricCurrent { + if drvDMA.gpioPadMemory == nil { + return 0 + } + var v pad + if p.number < 28 { + v = drvDMA.gpioPadMemory.pads0 + } else { + // GPIO 46~53 are not exposed. + v = drvDMA.gpioPadMemory.pads1 + } + switch v & 7 { + case padDrive2mA: + return 2 * physic.MilliAmpere + case padDrive4mA: + return 4 * physic.MilliAmpere + case padDrive6mA: + return 6 * physic.MilliAmpere + case padDrive8mA: + return 8 * physic.MilliAmpere + case padDrive10mA: + return 10 * physic.MilliAmpere + case padDrive12mA: + return 12 * physic.MilliAmpere + case padDrive14mA: + return 14 * physic.MilliAmpere + case padDrive16mA: + return 16 * physic.MilliAmpere + default: + return 0 + } +} + +// SlewLimit returns true if the output slew is limited to reduce interference. +// +// The slew is configurable per GPIO groups: 0~27 and 28~45. +// +// The default is true. +// +// Can only be used if driver bcm283x-dma was loaded. Otherwise returns false +// (the default value). +func (p *Pin) SlewLimit() bool { + if drvDMA.gpioPadMemory == nil { + return true + } + if p.number < 28 { + return drvDMA.gpioPadMemory.pads0&padSlewUnlimited == 0 + } + return drvDMA.gpioPadMemory.pads1&padSlewUnlimited == 0 +} + +// Hysteresis returns true if the input hysteresis via a Schmitt trigger is +// enabled. +// +// The hysteresis is configurable per GPIO groups: 0~27 and 28~45. +// +// The default is true. +// +// Can only be used if driver bcm283x-dma was loaded. Otherwise returns true +// (the default value). +func (p *Pin) Hysteresis() bool { + if drvDMA.gpioPadMemory == nil { + return true + } + if p.number < 28 { + return drvDMA.gpioPadMemory.pads0&padHysteresisEnable != 0 + } + return drvDMA.gpioPadMemory.pads1&padHysteresisEnable != 0 +} + +// Internal code. + +func (p *Pin) haltDMA() error { + if p.dmaCh != nil { + p.dmaCh.reset() + p.dmaCh = nil + } + if p.dmaBuf != nil { + if err := p.dmaBuf.Close(); err != nil { + return p.wrap(err) + } + p.dmaBuf = nil + } + return nil +} + +// haltClock disables the CLK/PWM clock if used. +func (p *Pin) haltClock() error { + if err := p.haltDMA(); err != nil { + return err + } + if !p.usingClock { + return nil + } + p.usingClock = false + + // Disable PWMx. + switch p.number { + // PWM0 is not used. + case 12, 18, 40: + // PWM1 + case 13, 19, 41, 45: + for _, i := range []int{13, 19, 41, 45} { + if cpuPins[i].usingClock { + return nil + } + } + shift := uint((p.number & 1) * 8) + drvDMA.pwmMemory.ctl &= ^(0xff << shift) + } + + // Disable PWM clock if nobody use. + for _, pin := range cpuPins { + if pin.usingClock { + return nil + } + } + err := resetPWMClockSource() + return err +} + +// function returns the current GPIO pin function. +func (p *Pin) function() function { + if drvGPIO.gpioMemory == nil { + return alt5 + } + return function((drvGPIO.gpioMemory.functionSelect[p.number/10] >> uint((p.number%10)*3)) & 7) +} + +// setFunction changes the GPIO pin function. +func (p *Pin) setFunction(f function) { + off := p.number / 10 + shift := uint(p.number%10) * 3 + drvGPIO.gpioMemory.functionSelect[off] = (drvGPIO.gpioMemory.functionSelect[off] &^ (7 << shift)) | (uint32(f) << shift) + // If a pin switches from a specific functionality back to GPIO, the alias + // should be updated. For example both GPIO13 and GPIO19 support PWM1. By + // default, PWM1 will be associated to GPIO13, even if + // GPIO19.SetFunc(gpio.PWM) is called. + // TODO(maruel): pinreg.Unregister() + // TODO(maruel): pinreg.Register() +} + +func (p *Pin) wrap(err error) error { + return fmt.Errorf("bcm283x-gpio (%s): %v", p, err) +} + +// + +// Each pin can have one of 7 functions. +const ( + in function = 0 + out function = 1 + alt0 function = 4 + alt1 function = 5 + alt2 function = 6 + alt3 function = 7 + alt4 function = 3 + alt5 function = 2 +) + +// cpuPins is all the pins as supported by the CPU. There is no guarantee that +// they are actually connected to anything on the board. +var cpuPins = []Pin{ + {number: 0, name: "GPIO0", defaultPull: gpio.PullUp}, + {number: 1, name: "GPIO1", defaultPull: gpio.PullUp}, + {number: 2, name: "GPIO2", defaultPull: gpio.PullUp}, + {number: 3, name: "GPIO3", defaultPull: gpio.PullUp}, + {number: 4, name: "GPIO4", defaultPull: gpio.PullUp}, + {number: 5, name: "GPIO5", defaultPull: gpio.PullUp}, + {number: 6, name: "GPIO6", defaultPull: gpio.PullUp}, + {number: 7, name: "GPIO7", defaultPull: gpio.PullUp}, + {number: 8, name: "GPIO8", defaultPull: gpio.PullUp}, + {number: 9, name: "GPIO9", defaultPull: gpio.PullDown}, + {number: 10, name: "GPIO10", defaultPull: gpio.PullDown}, + {number: 11, name: "GPIO11", defaultPull: gpio.PullDown}, + {number: 12, name: "GPIO12", defaultPull: gpio.PullDown}, + {number: 13, name: "GPIO13", defaultPull: gpio.PullDown}, + {number: 14, name: "GPIO14", defaultPull: gpio.PullDown}, + {number: 15, name: "GPIO15", defaultPull: gpio.PullDown}, + {number: 16, name: "GPIO16", defaultPull: gpio.PullDown}, + {number: 17, name: "GPIO17", defaultPull: gpio.PullDown}, + {number: 18, name: "GPIO18", defaultPull: gpio.PullDown}, + {number: 19, name: "GPIO19", defaultPull: gpio.PullDown}, + {number: 20, name: "GPIO20", defaultPull: gpio.PullDown}, + {number: 21, name: "GPIO21", defaultPull: gpio.PullDown}, + {number: 22, name: "GPIO22", defaultPull: gpio.PullDown}, + {number: 23, name: "GPIO23", defaultPull: gpio.PullDown}, + {number: 24, name: "GPIO24", defaultPull: gpio.PullDown}, + {number: 25, name: "GPIO25", defaultPull: gpio.PullDown}, + {number: 26, name: "GPIO26", defaultPull: gpio.PullDown}, + {number: 27, name: "GPIO27", defaultPull: gpio.PullDown}, + {number: 28, name: "GPIO28", defaultPull: gpio.Float}, + {number: 29, name: "GPIO29", defaultPull: gpio.Float}, + {number: 30, name: "GPIO30", defaultPull: gpio.PullDown}, + {number: 31, name: "GPIO31", defaultPull: gpio.PullDown}, + {number: 32, name: "GPIO32", defaultPull: gpio.PullDown}, + {number: 33, name: "GPIO33", defaultPull: gpio.PullDown}, + {number: 34, name: "GPIO34", defaultPull: gpio.PullUp}, + {number: 35, name: "GPIO35", defaultPull: gpio.PullUp}, + {number: 36, name: "GPIO36", defaultPull: gpio.PullUp}, + {number: 37, name: "GPIO37", defaultPull: gpio.PullDown}, + {number: 38, name: "GPIO38", defaultPull: gpio.PullDown}, + {number: 39, name: "GPIO39", defaultPull: gpio.PullDown}, + {number: 40, name: "GPIO40", defaultPull: gpio.PullDown}, + {number: 41, name: "GPIO41", defaultPull: gpio.PullDown}, + {number: 42, name: "GPIO42", defaultPull: gpio.PullDown}, + {number: 43, name: "GPIO43", defaultPull: gpio.PullDown}, + {number: 44, name: "GPIO44", defaultPull: gpio.Float}, + {number: 45, name: "GPIO45", defaultPull: gpio.Float}, + {number: 46, name: "GPIO46", defaultPull: gpio.PullUp}, +} + +// This excludes the functions in and out. +var mapping = [][6]pin.Func{ + {"I2C0_SDA"}, // 0 + {"I2C0_SCL"}, + {"I2C1_SDA"}, + {"I2C1_SCL"}, + {"CLK0"}, + {"CLK1"}, // 5 + {"CLK2"}, + {"SPI0_CS1"}, + {"SPI0_CS0"}, + {"SPI0_MISO"}, + {"SPI0_MOSI"}, // 10 + {"SPI0_CLK"}, + {"PWM0"}, + {"PWM1"}, + {"UART0_TX", "", "", "", "", "UART1_TX"}, + {"UART0_RX", "", "", "", "", "UART1_RX"}, // 15 + {"", "", "", "UART0_CTS", "SPI1_CS2", "UART1_CTS"}, + {"", "", "", "UART0_RTS", "SPI1_CS1", "UART1_RTS"}, + {"I2S_SCK", "", "", "", "SPI1_CS0", "PWM0"}, + {"I2S_WS", "", "", "", "SPI1_MISO", "PWM1"}, + {"I2S_DIN", "", "", "", "SPI1_MOSI", "CLK0"}, // 20 + {"I2S_DOUT", "", "", "", "SPI1_CLK", "CLK1"}, + {""}, + {""}, + {""}, + {""}, // 25 + {""}, + {""}, + {"I2C0_SDA", "", "I2S_SCK", "", "", ""}, + {"I2C0_SCL", "", "I2S_WS", "", "", ""}, + {"", "", "I2S_DIN", "UART0_CTS", "", "UART1_CTS"}, // 30 + {"", "", "I2S_DOUT", "UART0_RTS", "", "UART1_RTS"}, + {"CLK0", "", "", "UART0_TX", "", "UART1_TX"}, + {"", "", "", "UART0_RX", "", "UART1_RX"}, + {"CLK0"}, + {"SPI0_CS1"}, // 35 + {"SPI0_CS0", "", "UART0_TX", "", "", ""}, + {"SPI0_MISO", "", "UART0_RX", "", "", ""}, + {"SPI0_MOSI", "", "UART0_RTS", "", "", ""}, + {"SPI0_CLK", "", "UART0_CTS", "", "", ""}, + {"PWM0", "", "", "", "SPI2_MISO", "UART1_TX"}, // 40 + {"PWM1", "", "", "", "SPI2_MOSI", "UART1_RX"}, + {"CLK1", "", "", "", "SPI2_CLK", "UART1_RTS"}, + {"CLK2", "", "", "", "SPI2_CS0", "UART1_CTS"}, + {"CLK1", "I2C0_SDA", "I2C1_SDA", "", "SPI2_CS1", ""}, + {"PWM1", "I2C0_SCL", "I2C1_SCL", "", "SPI2_CS2", ""}, // 45 + {""}, +} + +// function specifies the active functionality of a pin. The alternative +// function is GPIO pin dependent. +type function uint8 + +// Mapping as +// https://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf +// pages 90-91. +type gpioMap struct { + // 0x00 RW GPIO Function Select 0 (GPIO0-9) + // 0x04 RW GPIO Function Select 1 (GPIO10-19) + // 0x08 RW GPIO Function Select 2 (GPIO20-29) + // 0x0C RW GPIO Function Select 3 (GPIO30-39) + // 0x10 RW GPIO Function Select 4 (GPIO40-49) + // 0x14 RW GPIO Function Select 5 (GPIO50-53) + functionSelect [6]uint32 // GPFSEL0~GPFSEL5 + // 0x18 - Reserved + dummy0 uint32 + // 0x1C W GPIO Pin Output Set 0 (GPIO0-31) + // 0x20 W GPIO Pin Output Set 1 (GPIO32-53) + outputSet [2]uint32 // GPSET0-GPSET1 + // 0x24 - Reserved + dummy1 uint32 + // 0x28 W GPIO Pin Output Clear 0 (GPIO0-31) + // 0x2C W GPIO Pin Output Clear 1 (GPIO32-53) + outputClear [2]uint32 // GPCLR0-GPCLR1 + // 0x30 - Reserved + dummy2 uint32 + // 0x34 R GPIO Pin Level 0 (GPIO0-31) + // 0x38 R GPIO Pin Level 1 (GPIO32-53) + level [2]uint32 // GPLEV0-GPLEV1 + // 0x3C - Reserved + dummy3 uint32 + // 0x40 RW GPIO Pin Event Detect Status 0 (GPIO0-31) + // 0x44 RW GPIO Pin Event Detect Status 1 (GPIO32-53) + eventDetectStatus [2]uint32 // GPEDS0-GPEDS1 + // 0x48 - Reserved + dummy4 uint32 + // 0x4C RW GPIO Pin Rising Edge Detect Enable 0 (GPIO0-31) + // 0x50 RW GPIO Pin Rising Edge Detect Enable 1 (GPIO32-53) + risingEdgeDetectEnable [2]uint32 // GPREN0-GPREN1 + // 0x54 - Reserved + dummy5 uint32 + // 0x58 RW GPIO Pin Falling Edge Detect Enable 0 (GPIO0-31) + // 0x5C RW GPIO Pin Falling Edge Detect Enable 1 (GPIO32-53) + fallingEdgeDetectEnable [2]uint32 // GPFEN0-GPFEN1 + // 0x60 - Reserved + dummy6 uint32 + // 0x64 RW GPIO Pin High Detect Enable 0 (GPIO0-31) + // 0x68 RW GPIO Pin High Detect Enable 1 (GPIO32-53) + highDetectEnable [2]uint32 // GPHEN0-GPHEN1 + // 0x6C - Reserved + dummy7 uint32 + // 0x70 RW GPIO Pin Low Detect Enable 0 (GPIO0-31) + // 0x74 RW GPIO Pin Low Detect Enable 1 (GPIO32-53) + lowDetectEnable [2]uint32 // GPLEN0-GPLEN1 + // 0x78 - Reserved + dummy8 uint32 + // 0x7C RW GPIO Pin Async Rising Edge Detect 0 (GPIO0-31) + // 0x80 RW GPIO Pin Async Rising Edge Detect 1 (GPIO32-53) + asyncRisingEdgeDetectEnable [2]uint32 // GPAREN0-GPAREN1 + // 0x84 - Reserved + dummy9 uint32 + // 0x88 RW GPIO Pin Async Falling Edge Detect 0 (GPIO0-31) + // 0x8C RW GPIO Pin Async Falling Edge Detect 1 (GPIO32-53) + asyncFallingEdgeDetectEnable [2]uint32 // GPAFEN0-GPAFEN1 + // 0x90 - Reserved + dummy10 uint32 + // 0x94 RW GPIO Pin Pull-up/down Enable (00=Float, 01=Down, 10=Up) + pullEnable uint32 // GPPUD + // 0x98 RW GPIO Pin Pull-up/down Enable Clock 0 (GPIO0-31) + // 0x9C RW GPIO Pin Pull-up/down Enable Clock 1 (GPIO32-53) + pullEnableClock [2]uint32 // GPPUDCLK0-GPPUDCLK1 + // 0xA0 - Reserved + dummy uint32 + // 0xB0 - Test (byte) +} + +// pad defines the settings for a GPIO pad group. +type pad uint32 + +const ( + padPasswd pad = 0x5A << 24 // Write protection + padSlewUnlimited pad = 1 << 4 // Output bandwidth limit to reduce bounce. + padHysteresisEnable pad = 1 << 3 // Schmitt trigger + padDrive2mA pad = 0 + padDrive4mA pad = 1 + padDrive6mA pad = 2 + padDrive8mA pad = 3 + padDrive10mA pad = 4 + padDrive12mA pad = 5 + padDrive14mA pad = 6 + padDrive16mA pad = 7 +) + +// set changes the current drive strength for the GPIO pad group. +// +// We could disable the schmitt trigger or the slew limit. +func (p *pad) set(settings pad) { + *p = padPasswd | settings +} + +func toPad(drive physic.ElectricCurrent, slewLimit, hysteresis bool) pad { + var p pad + d := int(drive / physic.MilliAmpere) + switch { + case d <= 2: + p = padDrive2mA + case d <= 4: + p = padDrive4mA + case d <= 6: + p = padDrive6mA + case d <= 8: + p = padDrive8mA + case d <= 10: + p = padDrive10mA + case d <= 12: + p = padDrive12mA + case d <= 14: + p = padDrive14mA + default: + p = padDrive16mA + } + if !slewLimit { + p |= padSlewUnlimited + } + if hysteresis { + p |= padHysteresisEnable + } + return p +} + +// Mapping as https://scribd.com/doc/101830961/GPIO-Pads-Control2 +type gpioPadMap struct { + dummy [11]uint32 // 0x00~0x28 + pads0 pad // 0x2c GPIO 0~27 + pads1 pad // 0x30 GPIO 28~45 + pads2 pad // 0x34 GPIO 46~53 +} + +func init() { + GPIO0 = &cpuPins[0] + GPIO1 = &cpuPins[1] + GPIO2 = &cpuPins[2] + GPIO3 = &cpuPins[3] + GPIO4 = &cpuPins[4] + GPIO5 = &cpuPins[5] + GPIO6 = &cpuPins[6] + GPIO7 = &cpuPins[7] + GPIO8 = &cpuPins[8] + GPIO9 = &cpuPins[9] + GPIO10 = &cpuPins[10] + GPIO11 = &cpuPins[11] + GPIO12 = &cpuPins[12] + GPIO13 = &cpuPins[13] + GPIO14 = &cpuPins[14] + GPIO15 = &cpuPins[15] + GPIO16 = &cpuPins[16] + GPIO17 = &cpuPins[17] + GPIO18 = &cpuPins[18] + GPIO19 = &cpuPins[19] + GPIO20 = &cpuPins[20] + GPIO21 = &cpuPins[21] + GPIO22 = &cpuPins[22] + GPIO23 = &cpuPins[23] + GPIO24 = &cpuPins[24] + GPIO25 = &cpuPins[25] + GPIO26 = &cpuPins[26] + GPIO27 = &cpuPins[27] + GPIO28 = &cpuPins[28] + GPIO29 = &cpuPins[29] + GPIO30 = &cpuPins[30] + GPIO31 = &cpuPins[31] + GPIO32 = &cpuPins[32] + GPIO33 = &cpuPins[33] + GPIO34 = &cpuPins[34] + GPIO35 = &cpuPins[35] + GPIO36 = &cpuPins[36] + GPIO37 = &cpuPins[37] + GPIO38 = &cpuPins[38] + GPIO39 = &cpuPins[39] + GPIO40 = &cpuPins[40] + GPIO41 = &cpuPins[41] + GPIO42 = &cpuPins[42] + GPIO43 = &cpuPins[43] + GPIO44 = &cpuPins[44] + GPIO45 = &cpuPins[45] + GPIO46 = &cpuPins[46] +} + +// Changing pull resistor require a 150 cycles sleep. +// +// Do not inline so the temporary value is not optimized out. +// +//go:noinline +func sleep150cycles() uint32 { + // Do not call into any kernel function, since this causes a high chance of + // being preempted. + // Abuse the fact that gpioMemory is uncached memory. + // TODO(maruel): No idea if this is too much or enough. + var out uint32 + for i := 0; i < 150; i++ { + out += drvGPIO.gpioMemory.functionSelect[0] + } + return out +} + +// driverGPIO implements periph.Driver. +type driverGPIO struct { + // baseAddr is the base for all the CPU registers. + // + // It is initialized by driverGPIO.Init(). + baseAddr uint32 + // dramBus is high bits to address uncached memory. See virtToUncachedPhys() + // in dma.go. + dramBus uint32 + // gpioMemory is the memory map of the CPU GPIO registers. + gpioMemory *gpioMap + // gpioBaseAddr is needed for DMA transfers. + gpioBaseAddr uint32 +} + +func (d *driverGPIO) Close() { + d.baseAddr = 0 + d.dramBus = 0 + d.gpioMemory = nil + d.gpioBaseAddr = 0 +} + +func (d *driverGPIO) String() string { + return "bcm283x-gpio" +} + +func (d *driverGPIO) Prerequisites() []string { + return nil +} + +func (d *driverGPIO) After() []string { + return []string{"sysfs-gpio"} +} + +func (d *driverGPIO) Init() (bool, error) { + if !Present() { + return false, errors.New("bcm283x CPU not detected") + } + model := distro.CPUInfo()["model name"] + if strings.Contains(model, "ARMv6") { + d.baseAddr = 0x20000000 + d.dramBus = 0x40000000 + } else { + // RPi2+ + d.baseAddr = 0x3F000000 + d.dramBus = 0xC0000000 + } + // Page 6. + // Virtual addresses in kernel mode will range between 0xC0000000 and + // 0xEFFFFFFF. + // Virtual addresses in user mode (i.e. seen by processes running in ARM + // Linux) will range between 0x00000000 and 0xBFFFFFFF. + // Peripherals (at physical address 0x20000000 on) are mapped into the kernel + // virtual address space starting at address 0xF2000000. Thus a peripheral + // advertised here at bus address 0x7Ennnnnn is available in the ARM kenel at + // virtual address 0xF2nnnnnn. + d.gpioBaseAddr = d.baseAddr + 0x200000 + + // Mark the right pins as available even if the memory map fails so they can + // callback to sysfs.Pins. + functions := map[pin.Func]struct{}{} + for i := range cpuPins { + name := cpuPins[i].name + num := strconv.Itoa(cpuPins[i].number) + + // Initializes the sysfs corresponding pin right away. + cpuPins[i].sysfsPin = sysfs.Pins[cpuPins[i].number] + + // Unregister the pin if already registered. This happens with sysfs-gpio. + // Do not error on it, since sysfs-gpio may have failed to load. + _ = gpioreg.Unregister(name) + _ = gpioreg.Unregister(num) + + if err := gpioreg.Register(&cpuPins[i]); err != nil { + return true, err + } + if err := gpioreg.RegisterAlias(num, name); err != nil { + return true, err + } + switch f := cpuPins[i].Func(); f { + case gpio.IN, gpio.OUT, gpio.IN_LOW, gpio.IN_HIGH, gpio.OUT_LOW, gpio.OUT_HIGH, pin.FuncNone: + default: + // Registering the same alias twice fails. This can happen if two pins + // are configured with the same function. For example both pin #12, #18 + // and #40 could be configured to work as PWM0. + if _, ok := functions[f]; !ok { + functions[f] = struct{}{} + if err := gpioreg.RegisterAlias(string(f), name); err != nil { + return true, err + } + } + } + } + + // Now do a second loop but do the alternate functions. + for i := range cpuPins { + for _, f := range cpuPins[i].SupportedFuncs() { + switch f { + case gpio.IN, gpio.OUT: + default: + if _, ok := functions[f]; !ok { + functions[f] = struct{}{} + if err := gpioreg.RegisterAlias(string(f), cpuPins[i].name); err != nil { + return true, err + } + } + } + } + } + + // Register some BCM-documentation specific names. + // Do not do UARTx_TXD/RXD nor the PCM_xxx ones. + aliases := [][2]string{ + {"GPCLK0", "CLK0"}, + {"GPCLK1", "CLK1"}, + {"GPCLK2", "CLK2"}, + {"PWM0_OUT", "PWM0"}, + {"PWM1_OUT", "PWM1"}, + } + for _, a := range aliases { + if err := gpioreg.RegisterAlias(a[0], a[1]); err != nil { + return true, err + } + } + + m, err := pmem.MapGPIO() + if err != nil { + // Try without /dev/gpiomem. This is the case of not running on Raspbian or + // raspbian before Jessie. This requires running as root. + var err2 error + m, err2 = pmem.Map(uint64(d.gpioBaseAddr), 4096) + var err error + if err2 != nil { + if distro.IsRaspbian() { + // Raspbian specific error code to help guide the user to troubleshoot + // the problems. + if os.IsNotExist(err) && os.IsPermission(err2) { + return true, fmt.Errorf("/dev/gpiomem wasn't found; please upgrade to Raspbian Jessie or run as root") + } + } + if os.IsPermission(err2) { + return true, fmt.Errorf("need more access, try as root: %v", err) + } + return true, err + } + } + if err := m.AsPOD(&d.gpioMemory); err != nil { + return true, err + } + + return true, sysfs.I2CSetSpeedHook(setSpeed) +} + +func setSpeed(f physic.Frequency) error { + // Writing to "/sys/module/i2c_bcm2708/parameters/baudrate" was confirmed to + // not work. + // modprobe hangs when a bus is opened, so this must be called *before* the + // bus is opened. + // TL;DR: we can't do anything here. + /* + if err := exec.Command("modprobe", "-r", "i2c_bcm2708").Run(); err != nil { + return fmt.Errorf("bcm283x: failed to unload driver i2c_bcm2708: %v", err) + } + if err := exec.Command("modprobe", "i2c_bcm2708", "baudrate=600000"); err != nil { + return fmt.Errorf("bcm283x: failed to reload driver i2c_bcm2708: %v", err) + } + */ + return errors.New("bcm283x: to change the I²C bus speed, please refer to https://periph.io/platform/raspberrypi/#i²c") +} + +func init() { + if isArm { + periph.MustRegister(&drvGPIO) + } +} + +var drvGPIO driverGPIO + +var _ gpio.PinIO = &Pin{} +var _ gpio.PinIn = &Pin{} +var _ gpio.PinOut = &Pin{} +var _ gpiostream.PinIn = &Pin{} +var _ gpiostream.PinOut = &Pin{} +var _ pin.PinFunc = &Pin{} diff --git a/vendor/periph.io/x/periph/host/bcm283x/pcm.go b/vendor/periph.io/x/periph/host/bcm283x/pcm.go new file mode 100644 index 0000000..7078de1 --- /dev/null +++ b/vendor/periph.io/x/periph/host/bcm283x/pcm.go @@ -0,0 +1,234 @@ +// 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. + +// pcm means I2S. + +package bcm283x + +import ( + "errors" + "fmt" + "time" + + "periph.io/x/periph/conn/physic" +) + +type pcmCS uint32 + +// Pages 126-129 +const ( + // 31:26 reserved + pcmStandby pcmCS = 1 << 25 // STBY Allow at least 4 PCM clock cycles to take effect + pcmSync pcmCS = 1 << 24 // SYNC Two PCM clocks have occurred since last write + pcmRXSignExtend pcmCS = 1 << 23 // RXSEX Sign extend RXZ data + pcmRXFull pcmCS = 1 << 22 // RXF RX FIFO is full + pcmTXEmpty pcmCS = 1 << 21 // TXE TX FIFO is empty + pcmRXData pcmCS = 1 << 20 // RXD RX FIFO contains data + pcmTXData pcmCS = 1 << 19 // TXD TX FIFO ready to accept data + pcmRXR pcmCS = 1 << 18 // RXR RX FIFO needs reading + pcmTXW pcmCS = 1 << 17 // TXW TX FIFO needs writing + pcmRXErr pcmCS = 1 << 16 // RXERR RX FIFO error + pcmTXErr pcmCS = 1 << 15 // TXERR TX FIFO error + pcmRXSync pcmCS = 1 << 14 // RXSYNC RX FIFO is out of sync + pcmTXSync pcmCS = 1 << 13 // TXSYNC TX FIFO is out of sync + // 12:10 reserved + pcmDMAEnable pcmCS = 1 << 9 // DMAEN Generate TX&RX DMA DREQ + // 8:7 RXTHR controls when pcmRXR is set + pcmRXThresholdOne pcmCS = 0 << 7 // One sample in RX FIFO + pcmRXThreshold1 pcmCS = 1 << 7 // RX FIFO is at least (?) full + pcmRXThreshold2 pcmCS = 2 << 7 // ? + pcmRXThresholdFull pcmCS = 3 << 7 // RX is full + // 6:5 TXTHR controls when pcmTXW is set + pcmTXThresholdEmpty pcmCS = 0 << 5 // TX FIFO is empty + pcmTXThresholdNotFull1 pcmCS = 1 << 5 // At least one sample can be put + pcmTXThresholdNotFull2 pcmCS = 2 << 5 // At least one sample can be put + pcmTXThresholdOne pcmCS = 3 << 5 // One sample can be put + pcmRXClear pcmCS = 1 << 4 // RXCLR Clear RX FIFO; takes 2 PCM clock to take effect + pcmTXClear pcmCS = 1 << 3 // TXCLR Clear TX FIFO; takes 2 PCM clock to take effect + pcmTXEnable pcmCS = 1 << 2 // TXON Enable TX + pcmRXEnable pcmCS = 1 << 1 // RXON Enable FX + pcmEnable pcmCS = 1 << 0 // EN Enable the PCM +) + +type pcmMode uint32 + +// Page 129-131 +const ( + // 31:29 reserved + pcmClockDisable pcmMode = 1 << 28 // CLK_DIS Cleanly disable the PCM clock + pcmDecimation32 pcmMode = 1 << 27 // PDMN; 0 is factor 16, 1 is factor 32 + pcmRXPDMFilter pcmMode = 1 << 26 // PDME Enable input CIC filter on PDM input + pcmRXMerge pcmMode = 1 << 25 // FRXP Merge both channels as single FIFO entry + pcmTXMerge pcmMode = 1 << 24 // FTXP Merge both channels as singe FIFO entry + pcmClockSlave pcmMode = 1 << 23 // CLKM PCM CLK is input + pcmClockInverted pcmMode = 1 << 22 // CLKI Inverse clock signal + pcmFSSlave pcmMode = 1 << 21 // FSM PCM FS is input + pcmFSInverted pcmMode = 1 << 20 // FSI Invese FS signal + pcmFrameLengthShift = 10 // + pcmFrameLenghtMask pcmMode = 0x3F << pcmFrameLengthShift // FLEN Frame length + 1 + pcmFSLenghtMask pcmMode = 0x3F << 0 // FSLEN FS pulse clock width +) + +type pcmRX uint32 + +// Page 131-132 +const ( + pcmRX1Width pcmRX = 1 << 31 // CH1WEX Legacy + pcmRX1Enable pcmRX = 1 << 30 // CH1EN + pcmRX1PosShift = 20 + pcmRX1PosMask pcmRX = 0x3F << pcmRX1PosShift // CH1POS Clock delay + pcmRX1Channel16 pcmRX = 8 << 16 // CH1WID (Arbitrary width between 8 and 16 is supported) + pcmRX2Width pcmRX = 1 << 15 // CH2WEX Legacy + pcmRX2Enable pcmRX = 1 << 14 // CH2EN + pcmRX2PosShift = 4 + pcmRX2PosMask pcmRX = 0x3F << pcmRX2PosShift // CH2POS Clock delay + pcmRX2Channel16 pcmRX = 8 << 0 // CH2WID (Arbitrary width between 8 and 16 is supported) +) + +type pcmTX uint32 + +// Page 133-134 +const ( + pcmTX1Width pcmTX = 1 << 31 // CH1WX Legacy + pcmTX1Enable pcmTX = 1 << 30 // CH1EN Enable channel 1 + pcmTX1PosShift = 20 + pcmTX1PosMask pcmTX = 0x3F << pcmTX1PosShift // CH1POS Clock delay + pcmTX1Channel16 pcmTX = 8 << 16 // CH1WID (Arbitrary width between 8 and 16 is supported) + pcmTX2Width pcmTX = 1 << 15 // CH2WEX Legacy + pcmTX2Enable pcmTX = 1 << 14 // CH2EN + pcmTX2PosShift = 4 + pcmTX2PosMask pcmTX = 0x3F << pcmTX2PosShift // CH2POS Clock delay + pcmTX2Channel16 pcmTX = 8 << 0 // CH2WID (Arbitrary width between 8 and 16 is supported) +) + +type pcmDreq uint32 + +// Page 134-135 +const ( + // 31 reserved + pcmDreqTXPanicShift = 24 + pcmDreqTXPanicMask pcmDreq = 0x7F << pcmDreqTXPanicShift // TX_PANIC Panic level + // 23 reserved + pcmDreqRXPanicShift = 16 + pcmDreqRXPanicMask pcmDreq = 0x7F << pcmDreqRXPanicShift // RX_PANIC Panic level + // 15 reserved + pcmDreqTXLevelShift = 8 + pcmDreqTXLevelMask pcmDreq = 0x7F << pcmDreqTXPanicShift // TX Request Level + // 7 reserved + pcmDreqRXLevelShift = 0 + pcmDreqRXLevelMask pcmDreq = 0x7F << pcmDreqRXPanicShift // RX Request Level +) + +type pcmInterrupt uint32 + +// Page 135 +const ( + // 31:4 reserved + pcmIntRXErr pcmInterrupt = 1 << 3 // RXERR RX error interrupt enable + pcmIntTXErr pcmInterrupt = 1 << 2 // TXERR TX error interrupt enable + pcmIntRXEnable pcmInterrupt = 1 << 1 // RXR RX Read interrupt enable + pcmIntTXEnable pcmInterrupt = 1 << 0 // TXW TX Write interrupt enable +) + +type pcmIntStatus uint32 + +// Page 135-136 +const ( + // 31:4 reserved + pcmIntStatRXErr pcmIntStatus = 1 << 3 // RXERR RX error occurred / clear + pcmIntStatTXErr pcmIntStatus = 1 << 2 // TXERR TX error occurred / clear + pcmIntStatRXEnable pcmIntStatus = 1 << 1 // RXR RX Read interrupt occurred / clear + pcmIntStatTXEnable pcmIntStatus = 1 << 0 // TXW TX Write interrupt occurred / clear + pcmIntStatusClear pcmIntStatus = 0xF +) + +// pcmGray puts it into a special data/strobe mode that is under 'best effort' +// contract. +type pcmGray uint32 + +// Page 136-137 +const ( + // 31:22 reserved + pcmGrayRXFIFOLevelShift = 16 + pcmGrayRXFIFOLevelMask pcmGray = 0x3F << pcmGrayRXFIFOLevelShift // RXFIFOLEVEL How many words in RXFIFO + pcmGrayFlushShift = 10 + pcmGrayFlushMask = 0x3F << pcmGrayFlushShift // FLUSHED How many bits were valid when flush occurred + pcmGrayRXLevelShift = 4 + pcmGrayRXLevelMask pcmGray = 0x3F << pcmGrayRXLevelShift // RXLEVEL How many GRAY coded bits received + pcmGrayFlush pcmGray = 1 << 2 // FLUSH + pcmGrayClear pcmGray = 1 << 1 // CLR + pcmGrayEnable pcmGray = 1 << 0 // EN +) + +// Page 119 +type pcmMap struct { + cs pcmCS // CS_A Control Status + fifo uint32 // FIFO_A FIFO register + mode pcmMode // MODE_A Operation mode + rxc pcmRX // RXC_A RX control + txc pcmTX // TXC_A TX control + dreq pcmDreq // DREQ_A DMA control + inten pcmInterrupt // INTEN_A Interrupt enable + intstc pcmIntStatus // INTSTC_A Interrupt status + gray pcmGray // GRAY Gray mode input processing +} + +func (p *pcmMap) GoString() string { + return fmt.Sprintf( + "{\n cs: 0x%x,\n mode: 0x%x,\n rxc: 0x%x,\n txc: 0x%x,\n dreq: 0x%x,\n inten: 0x%x,\n intstc: 0x%x,\n gray: 0x%x,\n}", + p.cs, p.mode, p.rxc, p.txc, p.dreq, p.inten, p.intstc, p.gray) +} + +func (p *pcmMap) reset() { + p.cs = 0 + // In theory need to wait the equivalent of 2 PCM clocks. + // TODO(maruel): Use pcmSync busy loop to synchronize. + Nanospin(time.Microsecond) + // Hard reset + p.fifo = 0 + p.mode = 0 + p.rxc = 0 + p.txc = 0 + p.dreq = 0 + p.inten = 0 + p.intstc = pcmIntStatusClear + p.gray = 0 + + // Clear pcmStandby / pcm +} + +// set initializes 8 bits stream via DMA with no delay and no FS. +func (p *pcmMap) set() { + p.cs |= pcmEnable + p.txc = pcmTX1Width | pcmTX1Channel16 | pcmTX1Enable // 32bit TX + p.mode = (32 - 1) << pcmFrameLengthShift + p.cs |= pcmTXClear | pcmRXClear + // In theory need to wait the equivalent of 2 PCM clocks. + // TODO(maruel): Use pcmSync busy loop to synchronize. + Nanospin(time.Microsecond) + p.dreq = 0x10<>bit<<0 | + (d[skip*1]&mask)>>bit<<1 | + (d[skip*2]&mask)>>bit<<2 | + (d[skip*3]&mask)>>bit<<3 | + (d[skip*4]&mask)>>bit<<4 | + (d[skip*5]&mask)>>bit<<5 | + (d[skip*6]&mask)>>bit<<6 | + (d[skip*7]&mask)>>bit<<7) + d = d[skip*8:] + } +} + +func getBit(b byte, index int, msb bool) byte { + var shift uint + if msb { + shift = uint(7 - index) + } else { + shift = uint(index) + } + return (b >> shift) & 1 +} + +func raster32Bits(s gpiostream.Stream, skip int, clear, set []uint32, mask uint32) error { + var msb bool + var bits []byte + switch b := s.(type) { + case *gpiostream.BitStream: + msb = !b.LSBF + bits = b.Bits + default: + return fmt.Errorf("unsupported type %T", b) + } + m := len(clear) / 8 + if n := len(bits); n < m { + m = n + } + index := 0 + for i := 0; i < m; i++ { + for j := 0; j < 8; j++ { + if getBit(bits[i], j, msb) != 0 { + for k := 0; k < skip; k++ { + set[index] |= mask + index++ + } + } else { + for k := 0; k < skip; k++ { + clear[index] |= mask + index++ + } + } + } + } + return nil +} + +// raster32 rasters the stream into a uint32 stream with the specified masks to +// put in the correctly slice when the bit is set and when it is clear. +// +// `s` must be one of the types in this package. +func raster32(s gpiostream.Stream, skip int, clear, set []uint32, mask uint32) error { + if mask == 0 { + return errors.New("bcm283x: mask is 0") + } + if len(clear) == 0 { + return errors.New("bcm283x: clear buffer is empty") + } + if len(set) == 0 { + return errors.New("bcm283x: set buffer is empty") + } + if len(clear) != len(set) { + return errors.New("bcm283x: clear and set buffers have different length") + } + switch x := s.(type) { + case *gpiostream.BitStream: + // TODO + return raster32Bits(x, skip, clear, set, mask) + case *gpiostream.EdgeStream: + return errors.New("bcm283x: EdgeStream is not supported yet") + case *gpiostream.Program: + return errors.New("bcm283x: Program is not supported yet") + default: + return errors.New("bcm283x: unknown stream type") + } +} + +// PCM/PWM DMA buf is encoded as little-endian and MSB first. +func copyStreamToDMABuf(w gpiostream.Stream, dst []uint32) error { + switch v := w.(type) { + case *gpiostream.BitStream: + if v.LSBF { + return errors.New("TODO(simokawa): handle BitStream.LSBF") + } + // This is big-endian and MSB first. + i := 0 + for ; i < len(v.Bits)/4; i++ { + dst[i] = binary.BigEndian.Uint32(v.Bits[i*4:]) + } + last := uint32(0) + if mod := len(v.Bits) % 4; mod > 0 { + for j := 0; j < mod; j++ { + last |= (uint32(v.Bits[i*4+j])) << uint32(8*(3-j)) + } + dst[i] = last + } + return nil + case *gpiostream.EdgeStream: + return errors.New("TODO(simokawa): handle EdgeStream") + default: + return errors.New("unsupported Stream type") + } +} diff --git a/vendor/periph.io/x/periph/host/bcm283x/timer.go b/vendor/periph.io/x/periph/host/bcm283x/timer.go new file mode 100644 index 0000000..4c6247e --- /dev/null +++ b/vendor/periph.io/x/periph/host/bcm283x/timer.go @@ -0,0 +1,60 @@ +// 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 bcm283x + +import ( + "time" + + "periph.io/x/periph/host/cpu" +) + +// ReadTime returns the time on a monotonic 1Mhz clock (1µs resolution). +// +// It only works if bcm283x-dma successfully loaded. Otherwise it returns 0. +func ReadTime() time.Duration { + if drvDMA.timerMemory == nil { + return 0 + } + return (time.Duration(drvDMA.timerMemory.high)<<32 | time.Duration(drvDMA.timerMemory.low)) * time.Microsecond +} + +// Nanospin spins the CPU without calling into the kernel code if possible. +func Nanospin(t time.Duration) { + start := ReadTime() + if start == 0 { + // Use the slow generic version. + cpu.Nanospin(t) + return + } + // TODO(maruel): Optimize code path for sub-1µs duration. + for ReadTime()-start < t { + } +} + +// + +const ( + // 31:4 reserved + timerM3 = 1 << 3 // M3 + timerM2 = 1 << 2 // M2 + timerM1 = 1 << 1 // M1 + timerM0 = 1 << 0 // M0 +) + +// Page 173 +type timerCtl uint32 + +// timerMap represents the registers to access the 1Mhz timer. +// +// Page 172 +type timerMap struct { + ctl timerCtl // CS + low uint32 // CLO + high uint32 // CHI + c0 uint32 // 0 + c1 uint32 // C1 + c2 uint32 // C2 + c3 uint32 // C3 +} diff --git a/vendor/periph.io/x/periph/host/beagle/black/black.go b/vendor/periph.io/x/periph/host/beagle/black/black.go new file mode 100644 index 0000000..1ff0317 --- /dev/null +++ b/vendor/periph.io/x/periph/host/beagle/black/black.go @@ -0,0 +1,34 @@ +// Copyright 2018 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 black implements headers for the BeagleBone Black and BeagleBone +// Black Wireless micro-computers. +// +// Reference +// +// https://beagleboard.org/black +// +// Datasheet +// +// https://elinux.org/Beagleboard:BeagleBoneBlack +// +// https://github.com/CircuitCo/BeagleBone-Black/blob/rev_b/BBB_SRM.pdf +// +// https://elinux.org/Beagleboard:Cape_Expansion_Headers +package black + +import ( + "strings" + + "periph.io/x/periph/host/distro" +) + +// Present returns true if the host is a BeagleBone Black or BeagleBone Black +// Wireless. +func Present() bool { + if isArm { + return strings.HasPrefix(distro.DTModel(), "TI AM335x BeagleBone Black") + } + return false +} diff --git a/vendor/periph.io/x/periph/host/beagle/black/black_arm.go b/vendor/periph.io/x/periph/host/beagle/black/black_arm.go new file mode 100644 index 0000000..9c85af9 --- /dev/null +++ b/vendor/periph.io/x/periph/host/beagle/black/black_arm.go @@ -0,0 +1,7 @@ +// Copyright 2018 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 black + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/beagle/black/black_other.go b/vendor/periph.io/x/periph/host/beagle/black/black_other.go new file mode 100644 index 0000000..1c146a7 --- /dev/null +++ b/vendor/periph.io/x/periph/host/beagle/black/black_other.go @@ -0,0 +1,9 @@ +// Copyright 2018 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. + +// +build !arm + +package black + +const isArm = false diff --git a/vendor/periph.io/x/periph/host/beagle/bone/bone.go b/vendor/periph.io/x/periph/host/beagle/bone/bone.go new file mode 100644 index 0000000..356e587 --- /dev/null +++ b/vendor/periph.io/x/periph/host/beagle/bone/bone.go @@ -0,0 +1,318 @@ +// Copyright 2018 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 bone implements headers J1, P8 and P9 found on many (but not all) +// BeagleBone micro-computer. +// +// In particular, the headers are found on the models using a TI AM335x +// processor: BeagleBone Black, Black Wireless, Green and Green Wireless. +// +// Reference +// +// http://beagleboard.org/Support/bone101/#hardware +package bone + +import ( + "errors" + + "periph.io/x/periph" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/conn/pin/pinreg" + "periph.io/x/periph/host/beagle/black" + "periph.io/x/periph/host/beagle/green" + "periph.io/x/periph/host/sysfs" +) + +// TODO(maruel): Use specialized am335x or pru implementation once available. + +// Common pin types on BeagleBones. +var ( + PWR_BUT = &pin.BasicPin{N: "PWR_BUT"} // + RESET_OUT = &pin.BasicPin{N: "RESET_OUT"} // SYS_RESETn + VADC = &pin.BasicPin{N: "VADC"} // VDD_ADC + AIN4 = &pin.BasicPin{N: "AIN4"} // AIN4 + AGND = &pin.BasicPin{N: "AGND"} // GNDA_ADC + AIN6 = &pin.BasicPin{N: "AIN6"} // AIN6 + AIN5 = &pin.BasicPin{N: "AIN5"} // AIN5 + AIN2 = &pin.BasicPin{N: "AIN2"} // AIN2 + AIN3 = &pin.BasicPin{N: "AIN3"} // AIN3 + AIN0 = &pin.BasicPin{N: "AIN0"} // AIN0 + AIN1 = &pin.BasicPin{N: "AIN1"} // AIN1 +) + +// Headers found on BeagleBones. +var ( + // Port J1 is the UART port where the default terminal is connected to. + J1_1 pin.Pin = pin.GROUND + J1_2 pin.Pin = pin.INVALID + J1_3 pin.Pin = pin.INVALID + J1_4 gpio.PinIO = gpio.INVALID // GPIO42, UART0_RX + J1_5 gpio.PinIO = gpio.INVALID // GPIO43, UART0_TX + J1_6 pin.Pin = pin.INVALID + + P8_1 pin.Pin = pin.GROUND + P8_2 pin.Pin = pin.GROUND + P8_3 gpio.PinIO = gpio.INVALID // GPIO38, MMC1_DAT6 + P8_4 gpio.PinIO = gpio.INVALID // GPIO39, MMC1_DAT7 + P8_5 gpio.PinIO = gpio.INVALID // GPIO34, MMC1_DAT2 + P8_6 gpio.PinIO = gpio.INVALID // GPIO35, MMC1_DAT3 + P8_7 gpio.PinIO = gpio.INVALID // GPIO66, Timer4 + P8_8 gpio.PinIO = gpio.INVALID // GPIO67, Timer7 + P8_9 gpio.PinIO = gpio.INVALID // GPIO69, Timer5 + P8_10 gpio.PinIO = gpio.INVALID // GPIO68, Timer6 + P8_11 gpio.PinIO = gpio.INVALID // GPIO45, + P8_12 gpio.PinIO = gpio.INVALID // GPIO44, + P8_13 gpio.PinIO = gpio.INVALID // GPIO23, EHRPWM2B + P8_14 gpio.PinIO = gpio.INVALID // GPIO26, + P8_15 gpio.PinIO = gpio.INVALID // GPIO47, + P8_16 gpio.PinIO = gpio.INVALID // GPIO46, + P8_17 gpio.PinIO = gpio.INVALID // GPIO27, + P8_18 gpio.PinIO = gpio.INVALID // GPIO65, + P8_19 gpio.PinIO = gpio.INVALID // GPIO22, EHRPWM2A + P8_20 gpio.PinIO = gpio.INVALID // GPIO63, MMC1_CMD + P8_21 gpio.PinIO = gpio.INVALID // GPIO62, MMC1_CLK + P8_22 gpio.PinIO = gpio.INVALID // GPIO37, MMC1_DAT5 + P8_23 gpio.PinIO = gpio.INVALID // GPIO36, MMC1_DAT4 + P8_24 gpio.PinIO = gpio.INVALID // GPIO33, MMC1_DAT1 + P8_25 gpio.PinIO = gpio.INVALID // GPIO32, MMC1_DAT0 + P8_26 gpio.PinIO = gpio.INVALID // GPIO61, + P8_27 gpio.PinIO = gpio.INVALID // GPIO86, LCD_VSYNC + P8_28 gpio.PinIO = gpio.INVALID // GPIO88, LCD_PCLK + P8_29 gpio.PinIO = gpio.INVALID // GPIO87, LCD_HSYNC + P8_30 gpio.PinIO = gpio.INVALID // GPIO89, LCD_AC_BIAS_E + P8_31 gpio.PinIO = gpio.INVALID // GPIO10, LCD_DATA14, UART4_CTS + P8_32 gpio.PinIO = gpio.INVALID // GPIO11, LCD_DATA15, UART5_RTS + P8_33 gpio.PinIO = gpio.INVALID // GPIO9, LCD_DATA13, UART4_RTS + P8_34 gpio.PinIO = gpio.INVALID // GPIO81, LCD_DATA11, EHRPWM1B, UART3_RTS + P8_35 gpio.PinIO = gpio.INVALID // GPIO8, LCD_DATA12, UART4_CTS + P8_36 gpio.PinIO = gpio.INVALID // GPIO80, LCD_DATA10, EHRPWM1A, UART3_CTS + P8_37 gpio.PinIO = gpio.INVALID // GPIO78, LCD_DATA8, UART5_TX + P8_38 gpio.PinIO = gpio.INVALID // GPIO79, LCD_DATA9, UART5_RX + P8_39 gpio.PinIO = gpio.INVALID // GPIO76, LCD_DATA6 + P8_40 gpio.PinIO = gpio.INVALID // GPIO77, LCD_DATA7 + P8_41 gpio.PinIO = gpio.INVALID // GPIO74, LCD_DATA4 + P8_42 gpio.PinIO = gpio.INVALID // GPIO75, LCD_DATA5 + P8_43 gpio.PinIO = gpio.INVALID // GPIO72, LCD_DATA2 + P8_44 gpio.PinIO = gpio.INVALID // GPIO73, LCD_DATA3 + P8_45 gpio.PinIO = gpio.INVALID // GPIO70, LCD_DATA0, EHRPWM2A + P8_46 gpio.PinIO = gpio.INVALID // GPIO71, LCD_DATA1, EHRPWM2B + + P9_1 pin.Pin = pin.GROUND + P9_2 pin.Pin = pin.GROUND + P9_3 pin.Pin = pin.V3_3 + P9_4 pin.Pin = pin.V3_3 + P9_5 pin.Pin = pin.V5 + P9_6 pin.Pin = pin.V5 + P9_7 pin.Pin = pin.V5 + P9_8 pin.Pin = pin.V5 + P9_9 pin.Pin = PWR_BUT // PWR_BUT + P9_10 pin.Pin = RESET_OUT // SYS_RESETn + P9_11 gpio.PinIO = gpio.INVALID // GPIO30, UART4_RX + P9_12 gpio.PinIO = gpio.INVALID // GPIO60 + P9_13 gpio.PinIO = gpio.INVALID // GPIO31, UART4_TX + P9_14 gpio.PinIO = gpio.INVALID // GPIO50, EHRPWM1A + P9_15 gpio.PinIO = gpio.INVALID // GPIO48 + P9_16 gpio.PinIO = gpio.INVALID // GPIO51, EHRPWM1B + P9_17 gpio.PinIO = gpio.INVALID // GPIO5, I2C1_SCL, SPI0_CS0 + P9_18 gpio.PinIO = gpio.INVALID // GPIO4, I2C1_SDA, SPI0_MISO + P9_19 gpio.PinIO = gpio.INVALID // GPIO13, I2C2_SCL, UART1_RTS, SPI1_CS1 + P9_20 gpio.PinIO = gpio.INVALID // GPIO12, I2C2_SDA, UART1_CTS, SPI1_CS0 + P9_21 gpio.PinIO = gpio.INVALID // GPIO3, EHRPWM0B, I2C2_SCL, UART2_TX, SPI0_MOSI + P9_22 gpio.PinIO = gpio.INVALID // GPIO2, EHRPWM0A, I2C2_SDA, UART2_RX, SPI0_CLK + P9_23 gpio.PinIO = gpio.INVALID // GPIO49 + P9_24 gpio.PinIO = gpio.INVALID // GPIO15, I2C1_SCL, UART1_TX + P9_25 gpio.PinIO = gpio.INVALID // GPIO117 + P9_26 gpio.PinIO = gpio.INVALID // GPIO14, I2C1_SDA, UART1_RX + P9_27 gpio.PinIO = gpio.INVALID // GPIO115 + P9_28 gpio.PinIO = gpio.INVALID // GPIO113, ECAPPWM2, SPI1_CS0 + P9_29 gpio.PinIO = gpio.INVALID // GPIO111, EHRPWM0B, SPI1_MOSI + P9_30 gpio.PinIO = gpio.INVALID // GPIO112, SPI1_MISO + P9_31 gpio.PinIO = gpio.INVALID // GPIO110, EHRPWM0A, SPI1_CLK + P9_32 pin.Pin = VADC // VDD_ADC + P9_33 pin.Pin = AIN4 // AIN4 + P9_34 pin.Pin = AGND // GNDA_ADC + P9_35 pin.Pin = AIN6 // AIN6 + P9_36 pin.Pin = AIN5 // AIN5 + P9_37 pin.Pin = AIN2 // AIN2 + P9_38 pin.Pin = AIN3 // AIN3 + P9_39 pin.Pin = AIN0 // AIN0 + P9_40 pin.Pin = AIN1 // AIN1 + P9_41 gpio.PinIO = gpio.INVALID // GPIO20 + P9_42 gpio.PinIO = gpio.INVALID // GPIO7, ECAPPWM0, UART3_TX, SPI1_CS1 + P9_43 pin.Pin = pin.GROUND + P9_44 pin.Pin = pin.GROUND + P9_45 pin.Pin = pin.GROUND + P9_46 pin.Pin = pin.GROUND +) + +// Present returns true if the host is a BeagleBone Black/Green or their +// Wireless version. +func Present() bool { + return black.Present() || green.Present() +} + +// driver implements periph.Driver. +type driver struct { +} + +func (d *driver) String() string { + return "beaglebone" +} + +func (d *driver) Prerequisites() []string { + return []string{"am335x"} +} + +func (d *driver) After() []string { + return nil +} + +func (d *driver) Init() (bool, error) { + if !Present() { + return false, errors.New("BeagleBone board not detected") + } + + J1_4 = sysfs.Pins[42] + J1_5 = sysfs.Pins[43] + + P8_3 = sysfs.Pins[38] + P8_4 = sysfs.Pins[39] + P8_5 = sysfs.Pins[34] + P8_6 = sysfs.Pins[35] + P8_7 = sysfs.Pins[66] + P8_8 = sysfs.Pins[67] + P8_9 = sysfs.Pins[69] + P8_10 = sysfs.Pins[68] + P8_11 = sysfs.Pins[45] + P8_12 = sysfs.Pins[44] + P8_13 = sysfs.Pins[23] + P8_14 = sysfs.Pins[26] + P8_15 = sysfs.Pins[47] + P8_16 = sysfs.Pins[46] + P8_17 = sysfs.Pins[27] + P8_18 = sysfs.Pins[65] + P8_19 = sysfs.Pins[22] + P8_20 = sysfs.Pins[63] + P8_21 = sysfs.Pins[62] + P8_22 = sysfs.Pins[37] + P8_23 = sysfs.Pins[36] + P8_24 = sysfs.Pins[33] + P8_25 = sysfs.Pins[32] + P8_26 = sysfs.Pins[61] + P8_27 = sysfs.Pins[86] + P8_28 = sysfs.Pins[88] + P8_29 = sysfs.Pins[87] + P8_30 = sysfs.Pins[89] + P8_31 = sysfs.Pins[10] + P8_32 = sysfs.Pins[11] + P8_33 = sysfs.Pins[9] + P8_34 = sysfs.Pins[81] + P8_35 = sysfs.Pins[8] + P8_36 = sysfs.Pins[80] + P8_37 = sysfs.Pins[78] + P8_38 = sysfs.Pins[79] + P8_39 = sysfs.Pins[76] + P8_40 = sysfs.Pins[77] + P8_41 = sysfs.Pins[74] + P8_42 = sysfs.Pins[75] + P8_43 = sysfs.Pins[72] + P8_44 = sysfs.Pins[73] + P8_45 = sysfs.Pins[70] + P8_46 = sysfs.Pins[71] + + P9_11 = sysfs.Pins[30] + P9_12 = sysfs.Pins[60] + P9_13 = sysfs.Pins[31] + P9_14 = sysfs.Pins[50] + P9_15 = sysfs.Pins[48] + P9_16 = sysfs.Pins[51] + P9_17 = sysfs.Pins[5] + P9_18 = sysfs.Pins[4] + P9_19 = sysfs.Pins[13] + P9_20 = sysfs.Pins[12] + P9_21 = sysfs.Pins[3] + P9_22 = sysfs.Pins[2] + P9_23 = sysfs.Pins[49] + P9_24 = sysfs.Pins[15] + P9_25 = sysfs.Pins[117] + P9_26 = sysfs.Pins[14] + P9_27 = sysfs.Pins[115] + P9_28 = sysfs.Pins[113] + P9_29 = sysfs.Pins[111] + P9_30 = sysfs.Pins[112] + P9_31 = sysfs.Pins[110] + P9_41 = sysfs.Pins[20] + P9_42 = sysfs.Pins[7] + + hdr := [][]pin.Pin{{J1_1}, {J1_2}, {J1_3}, {J1_4}, {J1_5}, {J1_6}} + if err := pinreg.Register("J1", hdr); err != nil { + return true, err + } + + hdr = [][]pin.Pin{ + {P8_1, P8_2}, + {P8_3, P8_4}, + {P8_5, P8_6}, + {P8_7, P8_8}, + {P8_9, P8_10}, + {P8_11, P8_12}, + {P8_13, P8_14}, + {P8_15, P8_16}, + {P8_17, P8_18}, + {P8_19, P8_20}, + {P8_21, P8_22}, + {P8_23, P8_24}, + {P8_25, P8_26}, + {P8_27, P8_28}, + {P8_29, P8_30}, + {P8_31, P8_32}, + {P8_33, P8_34}, + {P8_35, P8_36}, + {P8_37, P8_38}, + {P8_39, P8_40}, + {P8_41, P8_42}, + {P8_43, P8_44}, + {P8_45, P8_46}, + } + if err := pinreg.Register("P8", hdr); err != nil { + return true, err + } + + hdr = [][]pin.Pin{ + {P9_1, P9_2}, + {P9_3, P9_4}, + {P9_5, P9_6}, + {P9_7, P9_8}, + {P9_9, P9_10}, + {P9_11, P9_12}, + {P9_13, P9_14}, + {P9_15, P9_16}, + {P9_17, P9_18}, + {P9_19, P9_20}, + {P9_21, P9_22}, + {P9_23, P9_24}, + {P9_25, P9_26}, + {P9_27, P9_28}, + {P9_29, P9_30}, + {P9_31, P9_32}, + {P9_33, P9_34}, + {P9_35, P9_36}, + {P9_37, P9_38}, + {P9_39, P9_40}, + {P9_41, P9_42}, + {P9_43, P9_44}, + {P9_45, P9_46}, + } + err := pinreg.Register("P9", hdr) + return true, err +} + +func init() { + if isArm { + periph.MustRegister(&drv) + } +} + +var drv driver diff --git a/vendor/periph.io/x/periph/host/beagle/bone/bone_arm.go b/vendor/periph.io/x/periph/host/beagle/bone/bone_arm.go new file mode 100644 index 0000000..aa2c07a --- /dev/null +++ b/vendor/periph.io/x/periph/host/beagle/bone/bone_arm.go @@ -0,0 +1,7 @@ +// Copyright 2018 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 bone + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/beagle/bone/bone_other.go b/vendor/periph.io/x/periph/host/beagle/bone/bone_other.go new file mode 100644 index 0000000..199b584 --- /dev/null +++ b/vendor/periph.io/x/periph/host/beagle/bone/bone_other.go @@ -0,0 +1,9 @@ +// Copyright 2018 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. + +// +build !arm + +package bone + +const isArm = false diff --git a/vendor/periph.io/x/periph/host/beagle/green/green.go b/vendor/periph.io/x/periph/host/beagle/green/green.go new file mode 100644 index 0000000..7d25d3a --- /dev/null +++ b/vendor/periph.io/x/periph/host/beagle/green/green.go @@ -0,0 +1,95 @@ +// Copyright 2018 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 green implements headers for the BeagleBone Green and BeagleBone +// Green Wireless micro-computers. +// +// Reference +// +// https://beagleboard.org/green +// +// https://beagleboard.org/green-wireless +// +// Datasheet +// +// http://wiki.seeedstudio.com/BeagleBone_Green/ +package green + +import ( + "errors" + "strings" + + "periph.io/x/periph" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/conn/pin/pinreg" + "periph.io/x/periph/host/distro" + "periph.io/x/periph/host/sysfs" +) + +// Headers found on BeagleBone Green. +var ( + // I2C Groove port. + I2C_SCL gpio.PinIO = gpio.INVALID // GPIO13, I2C2_SCL, UART1_RTS, SPI1_CS1 + I2C_SDA gpio.PinIO = gpio.INVALID // GPIO12, I2C2_SDA, UART1_CTS, SPI1_CS0 + + // UART Groove port connected to UART2. + UART_TX gpio.PinIO = gpio.INVALID // GPIO3, EHRPWM0B, I2C2_SCL, UART2_TX, SPI0_MISO + UART_RX gpio.PinIO = gpio.INVALID // GPIO2, EHRPWM0A, I2C2_SDA, UART2_RX, SPI0_CLK +) + +// Present returns true if the host is a BeagleBone Green or BeagleBone Green +// Wireless. +func Present() bool { + if isArm { + return strings.HasPrefix(distro.DTModel(), "TI AM335x BeagleBone Green") + } + return false +} + +// driver implements periph.Driver. +type driver struct { +} + +func (d *driver) String() string { + return "beaglebone-green" +} + +func (d *driver) Prerequisites() []string { + return []string{"am335x"} +} + +func (d *driver) After() []string { + return nil +} + +func (d *driver) Init() (bool, error) { + if !Present() { + return false, errors.New("BeagleBone Green board not detected") + } + + I2C_SDA = sysfs.Pins[12] + I2C_SCL = sysfs.Pins[13] + hdr := [][]pin.Pin{{pin.GROUND}, {pin.V3_3}, {I2C_SDA}, {I2C_SCL}} + if err := pinreg.Register("I2C", hdr); err != nil { + return true, err + } + + UART_TX = sysfs.Pins[3] + UART_RX = sysfs.Pins[2] + hdr = [][]pin.Pin{{pin.GROUND}, {pin.V3_3}, {UART_TX}, {UART_RX}} + if err := pinreg.Register("UART", hdr); err != nil { + return true, err + } + + return true, nil +} + +func init() { + if isArm { + periph.MustRegister(&drv) + } +} + +var drv driver diff --git a/vendor/periph.io/x/periph/host/beagle/green/green_arm.go b/vendor/periph.io/x/periph/host/beagle/green/green_arm.go new file mode 100644 index 0000000..cc48c29 --- /dev/null +++ b/vendor/periph.io/x/periph/host/beagle/green/green_arm.go @@ -0,0 +1,7 @@ +// Copyright 2018 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 green + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/beagle/green/green_other.go b/vendor/periph.io/x/periph/host/beagle/green/green_other.go new file mode 100644 index 0000000..72de28f --- /dev/null +++ b/vendor/periph.io/x/periph/host/beagle/green/green_other.go @@ -0,0 +1,9 @@ +// Copyright 2018 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. + +// +build !arm + +package green + +const isArm = false diff --git a/vendor/periph.io/x/periph/host/chip/chip.go b/vendor/periph.io/x/periph/host/chip/chip.go new file mode 100644 index 0000000..19cd016 --- /dev/null +++ b/vendor/periph.io/x/periph/host/chip/chip.go @@ -0,0 +1,358 @@ +// Copyright 2016 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 chip + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + + "periph.io/x/periph" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/gpio/gpioreg" + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/conn/pin/pinreg" + "periph.io/x/periph/host/allwinner" + "periph.io/x/periph/host/distro" + "periph.io/x/periph/host/fs" +) + +// C.H.I.P. hardware pins. +var ( + TEMP_SENSOR = &pin.BasicPin{N: "TEMP_SENSOR"} + PWR_SWITCH = &pin.BasicPin{N: "PWR_SWITCH"} + // XIO "gpio" pins attached to the pcf8574 I²C port extender. + XIO0, XIO1, XIO2, XIO3, XIO4, XIO5, XIO6, XIO7 gpio.PinIO +) + +// The U13 header is opposite the power LED. +// +// The alternate pin functionality is described at pages 322-323 of +// https://github.com/NextThingCo/CHIP-Hardware/raw/master/CHIP%5Bv1_0%5D/CHIPv1_0-BOM-Datasheets/Allwinner%20R8%20User%20Manual%20V1.1.pdf +var ( + U13_1 = pin.GROUND // + U13_2 = pin.DC_IN // + U13_3 = pin.V5 // (filtered) + U13_4 = pin.GROUND // + U13_5 = pin.V3_3 // + U13_6 = TEMP_SENSOR // Analog temp sensor input + U13_7 = pin.V1_8 // + U13_8 = pin.BAT_PLUS // External LiPo battery + U13_9 = allwinner.PB16 // I2C1_SDA + U13_10 = PWR_SWITCH // Power button + U13_11 = allwinner.PB15 // I2C1_SCL + U13_12 = pin.GROUND // + U13_13 = allwinner.X1 // Touch screen X1 + U13_14 = allwinner.X2 // Touch screen X2 + U13_15 = allwinner.Y1 // Touch screen Y1 + U13_16 = allwinner.Y2 // Touch screen Y2 + U13_17 = allwinner.PD2 // LCD-D2; UART2_TX firmware probe for 1-wire to detect DIP at boot; http://docs.getchip.com/dip.html#dip-identification + U13_18 = allwinner.PB2 // PWM0; EINT16 + U13_19 = allwinner.PD4 // LCD-D4; UART2_CTS + U13_20 = allwinner.PD3 // LCD-D3; UART2_RX + U13_21 = allwinner.PD6 // LCD-D6 + U13_22 = allwinner.PD5 // LCD-D5 + U13_23 = allwinner.PD10 // LCD-D10 + U13_24 = allwinner.PD7 // LCD-D7 + U13_25 = allwinner.PD12 // LCD-D12 + U13_26 = allwinner.PD11 // LCD-D11 + U13_27 = allwinner.PD14 // LCD-D14 + U13_28 = allwinner.PD13 // LCD-D13 + U13_29 = allwinner.PD18 // LCD-D18 + U13_30 = allwinner.PD15 // LCD-D15 + U13_31 = allwinner.PD20 // LCD-D20 + U13_32 = allwinner.PD19 // LCD-D19 + U13_33 = allwinner.PD22 // LCD-D22 + U13_34 = allwinner.PD21 // LCD-D21 + U13_35 = allwinner.PD24 // LCD-CLK + U13_36 = allwinner.PD23 // LCD-D23 + U13_37 = allwinner.PD26 // LCD-VSYNC + U13_38 = allwinner.PD27 // LCD-HSYNC + U13_39 = pin.GROUND // + U13_40 = allwinner.PD25 // LCD-DE: RGB666 data +) + +// The U14 header is right next to the power LED. +var ( + U14_1 = pin.GROUND // + U14_2 = pin.V5 // (filtered) + U14_3 = allwinner.PG3 // UART1_TX; EINT3 + U14_4 = allwinner.HP_LEFT // Headphone left output + U14_5 = allwinner.PG4 // UART1_RX; EINT4 + U14_6 = allwinner.HP_COM // Headphone amp out + U14_7 = allwinner.FEL // Boot mode selection + U14_8 = allwinner.HP_RIGHT // Headphone right output + U14_9 = pin.V3_3 // + U14_10 = allwinner.MIC_GND // Microphone ground + U14_11 = allwinner.KEY_ADC // LRADC Low res analog to digital + U14_12 = allwinner.MIC_IN // Microphone input + U14_13 = XIO0 // gpio via I²C controller + U14_14 = XIO1 // gpio via I²C controller + U14_15 = XIO2 // gpio via I²C controller + U14_16 = XIO3 // gpio via I²C controller + U14_17 = XIO4 // gpio via I²C controller + U14_18 = XIO5 // gpio via I²C controller + U14_19 = XIO6 // gpio via I²C controller + U14_20 = XIO7 // gpio via I²C controller + U14_21 = pin.GROUND // + U14_22 = pin.GROUND // + U14_23 = allwinner.PG1 // GPS_CLK; AP-EINT1 + U14_24 = allwinner.PB3 // IR_TX; AP-EINT3 (EINT17) + U14_25 = allwinner.PB18 // I2C2_SDA + U14_26 = allwinner.PB17 // I2C2_SCL + U14_27 = allwinner.PE0 // CSIPCK: CMOS serial interface; SPI2_CS0; EINT14 + U14_28 = allwinner.PE1 // CSICK: CMOS serial interface; SPI2_CLK; EINT15 + U14_29 = allwinner.PE2 // CSIHSYNC; SPI2_MOSI + U14_30 = allwinner.PE3 // CSIVSYNC; SPI2_MISO + U14_31 = allwinner.PE4 // CSID0 + U14_32 = allwinner.PE5 // CSID1 + U14_33 = allwinner.PE6 // CSID2 + U14_34 = allwinner.PE7 // CSID3 + U14_35 = allwinner.PE8 // CSID4 + U14_36 = allwinner.PE9 // CSID5 + U14_37 = allwinner.PE10 // CSID6; UART1_RX + U14_38 = allwinner.PE11 // CSID7; UART1_TX + U14_39 = pin.GROUND // + U14_40 = pin.GROUND // +) + +// Present returns true if running on a NextThing Co's C.H.I.P. board. +// +// It looks for "C.H.I.P" in the device tree. The following information is +// expected in the device dtree: +// root@chip2:/proc/device-tree# od -c compatible +// 0000000 n e x t t h i n g , c h i p \0 a +// 0000020 l l w i n n e r , s u n 5 i - r +// 0000040 8 \0 +// root@chip2:/proc/device-tree# od -c model +// 0000000 N e x t T h i n g C . H . I . +// 0000020 P . \0 +func Present() bool { + return strings.Contains(distro.DTModel(), "C.H.I.P") +} + +// + +// aliases is a list of aliases for the various gpio pins, this allows users to +// refer to pins using the documented and labeled names instead of some GPIOnnn +// name. The map key is the alias and the value is the real pin name. +var aliases = map[string]string{ + "AP-EINT1": "PG1", + "AP-EINT3": "PB3", + "CSIPCK": "PE0", + "CSIHSYNC": "PE2", + "CSID0": "PE4", + "CSID2": "PE6", + "CSID4": "PE8", + "CSID6": "PE10", + "CSICK": "PE1", + "CSIVSYNC": "PE3", + "CSID1": "PE5", + "CSID3": "PE7", + "CSID5": "PE9", + "CSID7": "PE11", + "LCD-CLK": "PD24", + "LCD-D10": "PD10", + "LCD-D11": "PD11", + "LCD-D12": "PD12", + "LCD-D13": "PD13", + "LCD-D14": "PD14", + "LCD-D15": "PD15", + "LCD-D18": "PD18", + "LCD-D19": "PD19", + "LCD-D2": "PD2", + "LCD-D20": "PD20", + "LCD-D21": "PD21", + "LCD-D22": "PD22", + "LCD-D23": "PD23", + "LCD-D3": "PD3", + "LCD-D4": "PD4", + "LCD-D5": "PD5", + "LCD-D6": "PD6", + "LCD-D7": "PD7", + "LCD-DE": "PD25", + "LCD-HSYNC": "PD27", + "LCD-VSYNC": "PD26", + "TWI1-SCK": "PB15", + "TWI1-SDA": "PB16", + "TWI2-SCK": "PB17", + "TWI2-SDA": "PB18", + "UART1-RX": "PG4", + "UART1-TX": "PG3", +} + +func init() { + // These are initialized later by the driver. + XIO0 = gpio.INVALID + XIO1 = gpio.INVALID + XIO2 = gpio.INVALID + XIO3 = gpio.INVALID + XIO4 = gpio.INVALID + XIO5 = gpio.INVALID + XIO6 = gpio.INVALID + XIO7 = gpio.INVALID + // These must be reinitialized. + U14_13 = XIO0 + U14_14 = XIO1 + U14_15 = XIO2 + U14_16 = XIO3 + U14_17 = XIO4 + U14_18 = XIO5 + U14_19 = XIO6 + U14_20 = XIO7 +} + +// findXIOBase calculates the base of the XIO-P? gpio pins as explained in +// http://docs.getchip.com/chip.html#kernel-4-3-vs-4-4-gpio-how-to-tell-the-difference +// +// The XIO-P? sysfs mapped pin number changed in kernel 4.3, 4.4.11 and again +// in 4.4.13 so it is better to query sysfs. +func findXIOBase() int { + chips, err := filepath.Glob("/sys/class/gpio/gpiochip*/label") + if err != nil { + return -1 + } + for _, item := range chips { + f, err := fs.Open(item, os.O_RDONLY) + if err != nil { + continue + } + b, err := ioutil.ReadAll(f) + if err1 := f.Close(); err == nil { + err = err1 + } + if err != nil { + continue + } + if string(b) == "pcf8574a\n" { + id, err := strconv.Atoi(filepath.Base(filepath.Dir(item))[8:]) + if err != nil { + return -1 + } + return id + } + } + return -1 +} + +// driver implements drivers.Driver. +type driver struct { +} + +func (d *driver) String() string { + return "chip" +} + +func (d *driver) Prerequisites() []string { + return nil +} + +func (d *driver) After() []string { + // has allwinner cpu, needs sysfs for XIO0-XIO7 "gpio" pins + return []string{"allwinner-gpio", "sysfs-gpio"} +} + +func (d *driver) Init() (bool, error) { + if !Present() { + return false, errors.New("NextThing Co. CHIP board not detected") + } + + base := findXIOBase() + if base == -1 { + return true, errors.New("couldn't find XIO pins base number") + } + for i := 0; i < 8; i++ { + aliases[fmt.Sprintf("XIO-P%d", i)] = fmt.Sprintf("GPIO%d", base+i) + } + + // At this point the sysfs driver has initialized and discovered its pins, + // we can now hook-up the appropriate CHIP pins to sysfs gpio pins. + for alias, real := range aliases { + if err := gpioreg.RegisterAlias(alias, real); err != nil { + return true, err + } + } + // These must be explicitly initialized. + XIO0 = gpioreg.ByName("XIO-P0") + XIO1 = gpioreg.ByName("XIO-P1") + XIO2 = gpioreg.ByName("XIO-P2") + XIO3 = gpioreg.ByName("XIO-P3") + XIO4 = gpioreg.ByName("XIO-P4") + XIO5 = gpioreg.ByName("XIO-P5") + XIO6 = gpioreg.ByName("XIO-P6") + XIO7 = gpioreg.ByName("XIO-P7") + U14_13 = XIO0 + U14_14 = XIO1 + U14_15 = XIO2 + U14_16 = XIO3 + U14_17 = XIO4 + U14_18 = XIO5 + U14_19 = XIO6 + U14_20 = XIO7 + + // U13 is one of the 20x2 connectors. + U13 := [][]pin.Pin{ + {U13_1, U13_2}, + {U13_3, U13_4}, + {U13_5, U13_6}, + {U13_7, U13_8}, + {gpioreg.ByName("TWI1-SDA"), U13_10}, + {gpioreg.ByName("TWI1-SCK"), U13_12}, + {U13_13, U13_14}, + {U13_15, U13_16}, + {gpioreg.ByName("LCD-D2"), gpioreg.ByName("PWM0")}, + {gpioreg.ByName("LCD-D4"), gpioreg.ByName("LCD-D3")}, + {gpioreg.ByName("LCD-D6"), gpioreg.ByName("LCD-D5")}, + {gpioreg.ByName("LCD-D10"), gpioreg.ByName("LCD-D7")}, + {gpioreg.ByName("LCD-D12"), gpioreg.ByName("LCD-D11")}, + {gpioreg.ByName("LCD-D14"), gpioreg.ByName("LCD-D13")}, + {gpioreg.ByName("LCD-D18"), gpioreg.ByName("LCD-D15")}, + {gpioreg.ByName("LCD-D20"), gpioreg.ByName("LCD-D19")}, + {gpioreg.ByName("LCD-D22"), gpioreg.ByName("LCD-D21")}, + {gpioreg.ByName("LCD-CLK"), gpioreg.ByName("LCD-D23")}, + {gpioreg.ByName("LCD-VSYNC"), gpioreg.ByName("LCD-HSYNC")}, + {U13_39, gpioreg.ByName("LCD-DE")}, + } + if err := pinreg.Register("U13", U13); err != nil { + return true, err + } + + // U14 is one of the 20x2 connectors. + U14 := [][]pin.Pin{ + {U14_1, U14_2}, + {gpioreg.ByName("UART1-TX"), U14_4}, + {gpioreg.ByName("UART1-RX"), U14_6}, + {U14_7, U14_8}, + {U14_9, U14_10}, + {U14_11, U14_12}, // TODO(maruel): switch to LRADC once analog support is added + {U14_13, U14_14}, + {U14_15, U14_16}, + {U14_17, U14_18}, + {U14_19, U14_20}, + {U14_21, U14_22}, + {gpioreg.ByName("AP-EINT1"), gpioreg.ByName("AP-EINT3")}, + {gpioreg.ByName("TWI2-SDA"), gpioreg.ByName("TWI2-SCK")}, + {gpioreg.ByName("CSIPCK"), gpioreg.ByName("CSICK")}, + {gpioreg.ByName("CSIHSYNC"), gpioreg.ByName("CSIVSYNC")}, + {gpioreg.ByName("CSID0"), gpioreg.ByName("CSID1")}, + {gpioreg.ByName("CSID2"), gpioreg.ByName("CSID3")}, + {gpioreg.ByName("CSID4"), gpioreg.ByName("CSID5")}, + {gpioreg.ByName("CSID6"), gpioreg.ByName("CSID7")}, + {U14_39, U14_40}, + } + return true, pinreg.Register("U14", U14) +} + +func init() { + if isArm { + periph.MustRegister(&drv) + } +} + +var drv driver diff --git a/vendor/periph.io/x/periph/host/chip/chip_arm.go b/vendor/periph.io/x/periph/host/chip/chip_arm.go new file mode 100644 index 0000000..4b89be0 --- /dev/null +++ b/vendor/periph.io/x/periph/host/chip/chip_arm.go @@ -0,0 +1,7 @@ +// Copyright 2016 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 chip + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/chip/chip_other.go b/vendor/periph.io/x/periph/host/chip/chip_other.go new file mode 100644 index 0000000..2cf74a3 --- /dev/null +++ b/vendor/periph.io/x/periph/host/chip/chip_other.go @@ -0,0 +1,9 @@ +// Copyright 2016 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. + +// +build !arm + +package chip + +const isArm = false diff --git a/vendor/periph.io/x/periph/host/chip/doc.go b/vendor/periph.io/x/periph/host/chip/doc.go new file mode 100644 index 0000000..c55d43b --- /dev/null +++ b/vendor/periph.io/x/periph/host/chip/doc.go @@ -0,0 +1,30 @@ +// Copyright 2016 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 chip contains header definitions for NextThing Co's C.H.I.P. board. +// +// CHIP uses the Allwinner R8 processor and thus the allwinner host package is +// automatically imported. +// +// This package exports the U13 header, which is opposite the power LED, and +// U14, which is right next to the power LED. Most of the pins are usable as +// GPIO and are directly to the processor. These can use memory-mapped GPIO, +// which is very fast. The XIO-P0 through XIO-P7 pins are attached to a pcf8574 +// I²C expander which has the result that all accesses to these pins have to go +// through the kernel and the I²C bus protocol, i.e., they're slow. +// +// GPIO edge detection (using interrupts) is only supported on a few of the +// processor's pins: AP-EINT1, AP-EINT3, CSIPCK, and CSICK. Edge detection is +// also supported on the XIO pins, but this feature is rather limited due to +// the device and the driver (for example, the driver interrupts on all edges). +// +// References +// +// http://www.chip-community.org/index.php/Hardware_Information +// +// http://docs.getchip.com/chip.html#chip-hardware +// +// A graphical view of the board headers is available at: +// http://docs.getchip.com/chip.html#pin-headers +package chip diff --git a/vendor/periph.io/x/periph/host/cpu/cpu.go b/vendor/periph.io/x/periph/host/cpu/cpu.go new file mode 100644 index 0000000..c4e1ffd --- /dev/null +++ b/vendor/periph.io/x/periph/host/cpu/cpu.go @@ -0,0 +1,83 @@ +// Copyright 2016 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 cpu + +import ( + "io" + "io/ioutil" + "os" + "strconv" + "strings" + "sync" + "time" + + "periph.io/x/periph/host/fs" +) + +// MaxSpeed returns the processor maximum speed in Hz. +// +// Returns 0 if it couldn't be calculated. +func MaxSpeed() int64 { + if isLinux { + return getMaxSpeedLinux() + } + return 0 +} + +// Nanospin spins for a short amount of time doing a busy loop. +// +// This function should be called with durations of 10µs or less. +func Nanospin(d time.Duration) { + // TODO(maruel): Use runtime.LockOSThread()? + if isLinux { + nanospinLinux(d) + } else { + nanospinTime(d) + } +} + +// + +var ( + mu sync.Mutex + maxSpeed int64 = -1 + openFile = openFileOrig +) + +func openFileOrig(path string, flag int) (io.ReadCloser, error) { + f, err := fs.Open(path, flag) + if err != nil { + return nil, err + } + return f, nil +} + +func getMaxSpeedLinux() int64 { + mu.Lock() + defer mu.Unlock() + if maxSpeed == -1 { + maxSpeed = 0 + if f, err := openFile("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", os.O_RDONLY); err == nil { + defer f.Close() + if b, err := ioutil.ReadAll(f); err == nil { + s := strings.TrimSpace(string(b)) + if i, err := strconv.ParseInt(s, 10, 64); err == nil { + // Weirdly, the speed is listed as khz. :( + maxSpeed = i * 1000 + } + } + } + } + return maxSpeed +} + +func nanospinTime(d time.Duration) { + // TODO(maruel): That's not optimal; it's actually pretty bad. + // time.Sleep() sleeps for really too long, calling it repeatedly with + // minimal value will give the caller a wake rate of 5KHz or so, depending on + // the host. This makes it useless for bitbanging protocol implementations. + for start := time.Now(); time.Since(start) < d; { + } +} diff --git a/vendor/periph.io/x/periph/host/cpu/cpu_linux.go b/vendor/periph.io/x/periph/host/cpu/cpu_linux.go new file mode 100644 index 0000000..c227c88 --- /dev/null +++ b/vendor/periph.io/x/periph/host/cpu/cpu_linux.go @@ -0,0 +1,22 @@ +// Copyright 2016 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 cpu + +import ( + "syscall" + "time" +) + +const isLinux = true + +func nanospinLinux(d time.Duration) { + // runtime.nanotime() is not exported so it cannot be used to busy loop for + // very short sleep (10µs or less). + time := syscall.NsecToTimespec(d.Nanoseconds()) + leftover := syscall.Timespec{} + for syscall.Nanosleep(&time, &leftover) != nil { + time = leftover + } +} diff --git a/vendor/periph.io/x/periph/host/cpu/cpu_other.go b/vendor/periph.io/x/periph/host/cpu/cpu_other.go new file mode 100644 index 0000000..a473633 --- /dev/null +++ b/vendor/periph.io/x/periph/host/cpu/cpu_other.go @@ -0,0 +1,14 @@ +// Copyright 2016 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. + +// +build !linux + +package cpu + +import "time" + +const isLinux = false + +func nanospinLinux(d time.Duration) { +} diff --git a/vendor/periph.io/x/periph/host/cpu/doc.go b/vendor/periph.io/x/periph/host/cpu/doc.go new file mode 100644 index 0000000..34cd4de --- /dev/null +++ b/vendor/periph.io/x/periph/host/cpu/doc.go @@ -0,0 +1,6 @@ +// Copyright 2016 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 cpu implements functions relating to the host CPU itself. +package cpu diff --git a/vendor/periph.io/x/periph/host/distro/devtree.go b/vendor/periph.io/x/periph/host/distro/devtree.go new file mode 100644 index 0000000..d31eb70 --- /dev/null +++ b/vendor/periph.io/x/periph/host/distro/devtree.go @@ -0,0 +1,61 @@ +// Copyright 2016 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 distro + +// DTModel returns platform model info from the Linux device tree (/proc/device-tree/model), and +// returns "unknown" on non-linux systems or if the file is missing. +func DTModel() string { + mu.Lock() + defer mu.Unlock() + + if dtModel == "" { + dtModel = "" + if isLinux { + dtModel = makeDTModelLinux() + } + } + return dtModel +} + +// DTCompatible returns platform compatibility info from the Linux device tree +// (/proc/device-tree/compatible), and returns []{"unknown"} on non-linux systems or if the file is +// missing. +func DTCompatible() []string { + mu.Lock() + defer mu.Unlock() + + if dtCompatible == nil { + dtCompatible = []string{} + if isLinux { + dtCompatible = makeDTCompatible() + } + } + return dtCompatible +} + +// + +var ( + dtModel string // cached /proc/device-tree/model + dtCompatible []string // cached /proc/device-tree/compatible +) + +func makeDTModelLinux() string { + // Read model from device tree. + if bytes, err := readFile("/proc/device-tree/model"); err == nil { + if model := splitNull(bytes); len(model) > 0 { + return model[0] + } + } + return "" +} + +func makeDTCompatible() []string { + // Read compatible from device tree. + if bytes, err := readFile("/proc/device-tree/compatible"); err == nil { + return splitNull(bytes) + } + return []string{} +} diff --git a/vendor/periph.io/x/periph/host/distro/distro.go b/vendor/periph.io/x/periph/host/distro/distro.go new file mode 100644 index 0000000..96e49df --- /dev/null +++ b/vendor/periph.io/x/periph/host/distro/distro.go @@ -0,0 +1,189 @@ +// Copyright 2016 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 distro implements common functionality to auto-detect features on +// the host; generally about linux distributions. +// +// Most of the functions exported as in the form IsFoo() where Foo is a linux +// distribution. +package distro + +import ( + "io/ioutil" + "os" + "strconv" + "strings" + "sync" + "unicode" +) + +// IsArmbian returns true if running on a Armbian distribution. +// +// http://www.armbian.com/ +func IsArmbian() bool { + if isArm && isLinux { + // Armbian presents itself as debian in /etc/os-release so OSRelease() + // cannot be used.. + _, err := os.Stat("/etc/armbian.txt") + return err == nil + } + return false +} + +// IsDebian returns true if running on an Debian derived distribution. +// +// This function returns true on both Armbian, Raspbian and Ubuntu. +// +// https://debian.org/ +func IsDebian() bool { + if isLinux { + // http://0pointer.de/public/systemd-man/os-release.html#ID_LIKE= + if OSRelease()["ID"] == "debian" { + return true + } + for _, part := range strings.Split(OSRelease()["ID_LIKE"], " ") { + if part == "debian" { + return true + } + } + } + return false +} + +// IsRaspbian returns true if running on a Raspbian distribution. +// +// https://raspbian.org/ +func IsRaspbian() bool { + if isArm && isLinux { + return OSRelease()["ID"] == "raspbian" + } + return false +} + +// IsUbuntu returns true if running on an Ubuntu derived distribution. +// +// https://ubuntu.com/ +func IsUbuntu() bool { + if isLinux { + return OSRelease()["ID"] == "ubuntu" + } + return false +} + +// OSRelease returns parsed data from /etc/os-release. +// +// For more information, see +// http://0pointer.de/public/systemd-man/os-release.html +func OSRelease() map[string]string { + if isLinux { + return makeOSReleaseLinux() + } + return osRelease +} + +// CPU + +// CPUInfo returns parsed data from /proc/cpuinfo. +func CPUInfo() map[string]string { + if isLinux { + return makeCPUInfoLinux() + } + return cpuInfo +} + +// + +var ( + mu sync.Mutex + cpuInfo map[string]string + osRelease map[string]string + readFile = ioutil.ReadFile +) + +func splitSemiColon(content string) map[string]string { + // Strictly speaking this format isn't ok, there can be multiple group. + out := map[string]string{} + for _, line := range strings.Split(content, "\n") { + parts := strings.SplitN(line, ":", 2) + if len(parts) != 2 { + continue + } + // This format may have space around the ':'. + key := strings.TrimRightFunc(parts[0], unicode.IsSpace) + if len(key) == 0 || key[0] == '#' { + continue + } + // Ignore duplicate keys. + // TODO(maruel): Keep them all. + if _, ok := out[key]; !ok { + // Trim on both side, trailing space was observed on "Features" value. + out[key] = strings.TrimFunc(parts[1], unicode.IsSpace) + } + } + return out +} + +func splitStrict(content string) map[string]string { + out := map[string]string{} + for _, line := range strings.Split(content, "\n") { + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + continue + } + key := parts[0] + if len(key) == 0 || key[0] == '#' { + continue + } + // Overwrite previous key. + value := parts[1] + if len(value) > 2 && value[0] == '"' && value[len(value)-1] == '"' { + // Not exactly 100% right but #closeenough. See for more details + // https://www.freedesktop.org/software/systemd/man/os-release.html + var err error + value, err = strconv.Unquote(value) + if err != nil { + continue + } + } + out[key] = value + } + return out +} + +// splitNull returns the null-terminated strings in the data +func splitNull(data []byte) []string { + ss := strings.Split(string(data), "\x00") + // The last string is typically null-terminated, so remove empty string + // from end of array. + if len(ss) > 0 && len(ss[len(ss)-1]) == 0 { + ss = ss[:len(ss)-1] + } + return ss +} + +func makeCPUInfoLinux() map[string]string { + mu.Lock() + defer mu.Unlock() + if cpuInfo == nil { + cpuInfo = map[string]string{} + if bytes, err := readFile("/proc/cpuinfo"); err == nil { + cpuInfo = splitSemiColon(string(bytes)) + } + } + return cpuInfo +} + +func makeOSReleaseLinux() map[string]string { + mu.Lock() + defer mu.Unlock() + if osRelease == nil { + // This file may not exist on older distros. Send a PR if you want to have + // a specific fallback. + osRelease = map[string]string{} + if bytes, err := readFile("/etc/os-release"); err == nil { + osRelease = splitStrict(string(bytes)) + } + } + return osRelease +} diff --git a/vendor/periph.io/x/periph/host/distro/distro_arm.go b/vendor/periph.io/x/periph/host/distro/distro_arm.go new file mode 100644 index 0000000..90a2335 --- /dev/null +++ b/vendor/periph.io/x/periph/host/distro/distro_arm.go @@ -0,0 +1,7 @@ +// Copyright 2016 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 distro + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/distro/distro_arm64.go b/vendor/periph.io/x/periph/host/distro/distro_arm64.go new file mode 100644 index 0000000..d5734bc --- /dev/null +++ b/vendor/periph.io/x/periph/host/distro/distro_arm64.go @@ -0,0 +1,9 @@ +// Copyright 2016 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. + +// +build arm64 + +package distro + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/distro/distro_linux.go b/vendor/periph.io/x/periph/host/distro/distro_linux.go new file mode 100644 index 0000000..7679a8f --- /dev/null +++ b/vendor/periph.io/x/periph/host/distro/distro_linux.go @@ -0,0 +1,7 @@ +// Copyright 2016 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 distro + +const isLinux = true diff --git a/vendor/periph.io/x/periph/host/distro/distro_nonarm.go b/vendor/periph.io/x/periph/host/distro/distro_nonarm.go new file mode 100644 index 0000000..075cad5 --- /dev/null +++ b/vendor/periph.io/x/periph/host/distro/distro_nonarm.go @@ -0,0 +1,9 @@ +// Copyright 2016 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. + +// +build !arm,!arm64 + +package distro + +const isArm = false diff --git a/vendor/periph.io/x/periph/host/distro/distro_nonlinux.go b/vendor/periph.io/x/periph/host/distro/distro_nonlinux.go new file mode 100644 index 0000000..7fd1599 --- /dev/null +++ b/vendor/periph.io/x/periph/host/distro/distro_nonlinux.go @@ -0,0 +1,9 @@ +// Copyright 2016 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. + +// +build !linux + +package distro + +const isLinux = false diff --git a/vendor/periph.io/x/periph/host/doc.go b/vendor/periph.io/x/periph/host/doc.go new file mode 100644 index 0000000..ee1b9af --- /dev/null +++ b/vendor/periph.io/x/periph/host/doc.go @@ -0,0 +1,10 @@ +// Copyright 2016 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 host defines the host itself. +// +// The host is the machine where this code is running. +// +// Subpackages contain the drivers that are loaded automatically. +package host diff --git a/vendor/periph.io/x/periph/host/fs/fs.go b/vendor/periph.io/x/periph/host/fs/fs.go new file mode 100644 index 0000000..5b717eb --- /dev/null +++ b/vendor/periph.io/x/periph/host/fs/fs.go @@ -0,0 +1,100 @@ +// 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 fs provides access to the file system on the host. +// +// It exposes ioctl syscall and epoll in an OS agnostic way and permits +// completely disabling file access to lock down unit tests. +package fs + +import ( + "errors" + "os" + "sync" +) + +// Ioctler is a file handle that supports ioctl calls. +type Ioctler interface { + // Ioctl sends a linux ioctl on the file handle. + // + // op is effectively an uint32. op is expected to be encoded in the format on + // x64. ARM happens to share the same format. + Ioctl(op uint, data uintptr) error +} + +// Open opens a file. +// +// Returns an error if Inhibit() was called. +func Open(path string, flag int) (*File, error) { + mu.Lock() + if inhibited { + mu.Unlock() + return nil, errors.New("file I/O is inhibited") + } + used = true + mu.Unlock() + + f, err := os.OpenFile(path, flag, 0600) + if err != nil { + return nil, err + } + return &File{f}, nil +} + +// Inhibit inhibits any future file I/O. It panics if any file was opened up to +// now. +// +// It should only be called in unit tests. +func Inhibit() { + mu.Lock() + inhibited = true + if used { + panic("calling Inhibit() while files were already opened") + } + mu.Unlock() +} + +// File is a superset of os.File. +type File struct { + *os.File +} + +// Ioctl sends an ioctl to the file handle. +func (f *File) Ioctl(op uint, data uintptr) error { + return ioctl(f.Fd(), op, data) +} + +// Event is a file system event. +type Event struct { + event +} + +// MakeEvent initializes an epoll *edge* triggered event on linux. +// +// An edge triggered event is basically an "auto-reset" event, where waiting on +// the edge resets it. A level triggered event requires manual resetting; this +// could be done via a Read() call but there's no need to require the user to +// call Read(). This is particularly useless in the case of gpio.RisingEdge and +// gpio.FallingEdge. +// +// As per the official doc, edge triggers is still remembered even when no +// epoll_wait() call is running, so no edge is missed. Two edges will be +// coallesced into one if the user mode process can't keep up. There's no +// accumulation of edges. +func (e *Event) MakeEvent(fd uintptr) error { + return e.event.makeEvent(fd) +} + +// Wait waits for an event or the specified amount of time. +func (e *Event) Wait(timeoutms int) (int, error) { + return e.event.wait(timeoutms) +} + +// + +var ( + mu sync.Mutex + inhibited bool + used bool +) diff --git a/vendor/periph.io/x/periph/host/fs/fs_linux.go b/vendor/periph.io/x/periph/host/fs/fs_linux.go new file mode 100644 index 0000000..db6a030 --- /dev/null +++ b/vendor/periph.io/x/periph/host/fs/fs_linux.go @@ -0,0 +1,124 @@ +// 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 fs + +import ( + "strconv" + "strings" + "syscall" +) + +const isLinux = true + +// syscall.EpollCtl() commands. +// +// These are defined here so we don't need to import golang.org/x/sys/unix. +// +// http://man7.org/linux/man-pages/man2/epoll_ctl.2.html +const ( + epollCTLAdd = 1 // EPOLL_CTL_ADD + epollCTLDel = 2 // EPOLL_CTL_DEL + epollCTLMod = 3 // EPOLL_CTL_MOD +) + +// Bitmask for field syscall.EpollEvent.Events. +// +// These are defined here so we don't need to import golang.org/x/sys/unix. +// +// http://man7.org/linux/man-pages/man2/epoll_ctl.2.html +type epollEvent uint32 + +const ( + epollIN epollEvent = 0x1 // EPOLLIN: available for read + epollOUT epollEvent = 0x4 // EPOLLOUT: available for write + epollPRI epollEvent = 0x2 // EPOLLPRI: exceptional urgent condition + epollERR epollEvent = 0x8 // EPOLLERR: error + epollHUP epollEvent = 0x10 // EPOLLHUP: hangup + epollET epollEvent = 0x80000000 // EPOLLET: Edge Triggered behavior + epollONESHOT epollEvent = 0x40000000 // EPOLLONESHOT: One shot + epollWAKEUP epollEvent = 0x20000000 // EPOLLWAKEUP: disable system sleep; kernel >=3.5 + epollEXCLUSIVE epollEvent = 0x10000000 // EPOLLEXCLUSIVE: only wake one; kernel >=4.5 +) + +var bitmaskString = [...]struct { + e epollEvent + s string +}{ + {epollIN, "IN"}, + {epollOUT, "OUT"}, + {epollPRI, "PRI"}, + {epollERR, "ERR"}, + {epollHUP, "HUP"}, + {epollET, "ET"}, + {epollONESHOT, "ONESHOT"}, + {epollWAKEUP, "WAKEUP"}, + {epollEXCLUSIVE, "EXCLUSIVE"}, +} + +// String is useful for debugging. +func (e epollEvent) String() string { + var out []string + for _, b := range bitmaskString { + if e&b.e != 0 { + out = append(out, b.s) + e &^= b.e + } + } + if e != 0 { + out = append(out, "0x"+strconv.FormatUint(uint64(e), 16)) + } + if len(out) == 0 { + out = []string{"0"} + } + return strings.Join(out, "|") +} + +func ioctl(f uintptr, op uint, arg uintptr) error { + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, f, uintptr(op), arg); errno != 0 { + return syscall.Errno(errno) + } + return nil +} + +type event struct { + event [1]syscall.EpollEvent + epollFd int + fd int +} + +// makeEvent creates an epoll *edge* triggered event. +// +// References: +// behavior and flags: http://man7.org/linux/man-pages/man7/epoll.7.html +// syscall.EpollCreate: http://man7.org/linux/man-pages/man2/epoll_create.2.html +// syscall.EpollCtl: http://man7.org/linux/man-pages/man2/epoll_ctl.2.html +func (e *event) makeEvent(fd uintptr) error { + epollFd, err := syscall.EpollCreate(1) + switch { + case err == nil: + break + case err.Error() == "function not implemented": + // Some arch (arm64) do not implement EpollCreate(). + if epollFd, err = syscall.EpollCreate1(0); err != nil { + return err + } + default: + return err + } + e.epollFd = epollFd + e.fd = int(fd) + // EPOLLWAKEUP could be used to force the system to not go do sleep while + // waiting for an edge. This is generally a bad idea, as we'd instead have + // the system to *wake up* when an edge is triggered. Achieving this is + // outside the scope of this interface. + e.event[0].Events = uint32(epollPRI | epollET) + e.event[0].Fd = int32(e.fd) + return syscall.EpollCtl(e.epollFd, epollCTLAdd, e.fd, &e.event[0]) +} + +func (e *event) wait(timeoutms int) (int, error) { + // http://man7.org/linux/man-pages/man2/epoll_wait.2.html + return syscall.EpollWait(e.epollFd, e.event[:], timeoutms) +} diff --git a/vendor/periph.io/x/periph/host/fs/fs_other.go b/vendor/periph.io/x/periph/host/fs/fs_other.go new file mode 100644 index 0000000..d1157d5 --- /dev/null +++ b/vendor/periph.io/x/periph/host/fs/fs_other.go @@ -0,0 +1,25 @@ +// 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. + +// +build !linux + +package fs + +import "errors" + +const isLinux = false + +func ioctl(f uintptr, op uint, arg uintptr) error { + return errors.New("fs: ioctl not supported on non-linux") +} + +type event struct{} + +func (e *event) makeEvent(f uintptr) error { + return errors.New("fs: unreachable code") +} + +func (e *event) wait(timeoutms int) (int, error) { + return 0, errors.New("fs: unreachable code") +} diff --git a/vendor/periph.io/x/periph/host/fs/ioctl.go b/vendor/periph.io/x/periph/host/fs/ioctl.go new file mode 100644 index 0000000..4e8320c --- /dev/null +++ b/vendor/periph.io/x/periph/host/fs/ioctl.go @@ -0,0 +1,51 @@ +// Copyright 2019 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 fs + +// These constants, variables and functions are ported from the Linux userland +// API header ioctl.h (commonly packaged at /usr/include/linux/ioctl.h which +// includes /usr/include/asm-generic/ioctl.h). + +const ( + iocNrbits uint = 8 + iocTypebits uint = 8 + + iocNrshift uint = 0 + + iocTypeshift = iocNrshift + iocNrbits + iocSizeshift = iocTypeshift + iocTypebits + iocDirshift = iocSizeshift + iocSizebits +) + +func ioc(dir, typ, nr, size uint) uint { + return (dir << iocDirshift) | + (typ << iocTypeshift) | + (nr << iocNrshift) | + (size << iocSizeshift) +} + +// IO defines an ioctl with no parameters. It corresponds to _IO in the Linux +// userland API. +func IO(typ, nr uint) uint { + return ioc(iocNone, typ, nr, 0) +} + +// IOR defines an ioctl with read (userland perspective) parameters. It +// corresponds to _IOR in the Linux userland API. +func IOR(typ, nr, size uint) uint { + return ioc(iocRead, typ, nr, size) +} + +// IOW defines an ioctl with write (userland perspective) parameters. It +// corresponds to _IOW in the Linux userland API. +func IOW(typ, nr, size uint) uint { + return ioc(iocWrite, typ, nr, size) +} + +// IOWR defines an ioctl with both read and write parameters. It corresponds to +// _IOWR in the Linux userland API. +func IOWR(typ, nr, size uint) uint { + return ioc(iocRead|iocWrite, typ, nr, size) +} diff --git a/vendor/periph.io/x/periph/host/fs/ioctl_mips_like.go b/vendor/periph.io/x/periph/host/fs/ioctl_mips_like.go new file mode 100644 index 0000000..e396c4c --- /dev/null +++ b/vendor/periph.io/x/periph/host/fs/ioctl_mips_like.go @@ -0,0 +1,16 @@ +// Copyright 2018 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. + +// +build mips mipsle + +package fs + +const ( + iocNone uint = 1 + iocRead uint = 2 + iocWrite uint = 4 + + iocSizebits uint = 13 + iocDirbits uint = 3 +) diff --git a/vendor/periph.io/x/periph/host/fs/ioctl_other.go b/vendor/periph.io/x/periph/host/fs/ioctl_other.go new file mode 100644 index 0000000..23472d7 --- /dev/null +++ b/vendor/periph.io/x/periph/host/fs/ioctl_other.go @@ -0,0 +1,16 @@ +// Copyright 2018 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. + +// +build !mips,!mipsle + +package fs + +const ( + iocNone uint = 0 + iocWrite uint = 1 + iocRead uint = 2 + + iocSizebits uint = 14 + iocDirbits uint = 2 +) diff --git a/vendor/periph.io/x/periph/host/host.go b/vendor/periph.io/x/periph/host/host.go new file mode 100644 index 0000000..64ccef9 --- /dev/null +++ b/vendor/periph.io/x/periph/host/host.go @@ -0,0 +1,15 @@ +// Copyright 2016 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 host + +import "periph.io/x/periph" + +// Init calls periph.Init() and returns it as-is. +// +// The only difference is that by calling host.Init(), you are guaranteed to +// have all the drivers implemented in this library to be implicitly loaded. +func Init() (*periph.State, error) { + return periph.Init() +} diff --git a/vendor/periph.io/x/periph/host/host_arm.go b/vendor/periph.io/x/periph/host/host_arm.go new file mode 100644 index 0000000..3227b9a --- /dev/null +++ b/vendor/periph.io/x/periph/host/host_arm.go @@ -0,0 +1,20 @@ +// Copyright 2016 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 host + +import ( + // Make sure CPU and board drivers are registered. + _ "periph.io/x/periph/host/allwinner" + _ "periph.io/x/periph/host/am335x" + _ "periph.io/x/periph/host/bcm283x" + _ "periph.io/x/periph/host/beagle/bone" + _ "periph.io/x/periph/host/beagle/green" + _ "periph.io/x/periph/host/chip" + _ "periph.io/x/periph/host/odroidc1" + // While this board is ARM64, it may run ARM 32 bits binaries so load it on + // 32 bits builds too. + _ "periph.io/x/periph/host/pine64" + _ "periph.io/x/periph/host/rpi" +) diff --git a/vendor/periph.io/x/periph/host/host_arm64.go b/vendor/periph.io/x/periph/host/host_arm64.go new file mode 100644 index 0000000..fdeb96f --- /dev/null +++ b/vendor/periph.io/x/periph/host/host_arm64.go @@ -0,0 +1,13 @@ +// Copyright 2016 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 host + +import ( + // Make sure CPU and board drivers are registered. + _ "periph.io/x/periph/host/allwinner" + _ "periph.io/x/periph/host/bcm283x" + _ "periph.io/x/periph/host/pine64" + _ "periph.io/x/periph/host/rpi" +) diff --git a/vendor/periph.io/x/periph/host/host_linux.go b/vendor/periph.io/x/periph/host/host_linux.go new file mode 100644 index 0000000..72f93a7 --- /dev/null +++ b/vendor/periph.io/x/periph/host/host_linux.go @@ -0,0 +1,10 @@ +// Copyright 2016 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 host + +import ( + // Make sure sysfs drivers are registered. + _ "periph.io/x/periph/host/sysfs" +) diff --git a/vendor/periph.io/x/periph/host/odroidc1/doc.go b/vendor/periph.io/x/periph/host/odroidc1/doc.go new file mode 100644 index 0000000..c004893 --- /dev/null +++ b/vendor/periph.io/x/periph/host/odroidc1/doc.go @@ -0,0 +1,26 @@ +// Copyright 2016 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 odroidc1 contains header definitions for Hardkernel's ODROID C0, C1, +// and C1+ boards. +// +// These boards use an Amlogic S805 processor (called "meson_8b" in the linux +// kernel). Currently no package for memory-mapped I/O has been written for +// this processor, thus all gpio functions are implemented via sysfs. +// +// This package only exports the main J2 header, which is rPi compatible except +// for a couple of analog pins (which are not currently supported). The J2 +// header has two I²C buses on header pins 3/5 and 27/28, the I²C functionality +// can be enabled by loading the aml_i2c kernel module. It has one SPI bus on +// header pins 19/21/23/24. The onewire gpio driver appears to be loaded by +// default on header pin 7. +// +// References +// +// Product page: http://www.hardkernel.com/main/products/prdt_info.php?g_code=G143703355573&tab_idx=2 +// +// Hardware wiki: http://odroid.com/dokuwiki/doku.php?id=en:c1_hardware +// +// Ubuntu drivers: http://odroid.com/dokuwiki/doku.php?id=en:odroid-c1#ubuntu +package odroidc1 diff --git a/vendor/periph.io/x/periph/host/odroidc1/odroidc1.go b/vendor/periph.io/x/periph/host/odroidc1/odroidc1.go new file mode 100644 index 0000000..2c885b4 --- /dev/null +++ b/vendor/periph.io/x/periph/host/odroidc1/odroidc1.go @@ -0,0 +1,195 @@ +// Copyright 2016 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 odroidc1 + +import ( + "errors" + "strconv" + "strings" + + "periph.io/x/periph" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/gpio/gpioreg" + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/conn/pin/pinreg" + "periph.io/x/periph/host/distro" + "periph.io/x/periph/host/sysfs" +) + +// The J2 header is rPi compatible, except for the two analog pins and the 1.8V +// output. +var ( + J2_1 = pin.V3_3 // 3.3V; max 30mA + J2_2 = pin.V5 // 5V (after filtering) + J2_3 gpio.PinIO = gpio.INVALID // I2C1_SDA + J2_4 = pin.V5 // 5V (after filtering) + J2_5 gpio.PinIO = gpio.INVALID // I2C1_SCL + J2_6 = pin.GROUND // + J2_7 gpio.PinIO = gpio.INVALID // CLK0 + J2_8 gpio.PinIO = gpio.INVALID // UART0_TX, UART1_TX + J2_9 = pin.GROUND // + J2_10 gpio.PinIO = gpio.INVALID // UART0_RX, UART1_RX + J2_11 gpio.PinIO = gpio.INVALID // UART0_RTS, SPI1_CS1, UART1_RTS + J2_12 gpio.PinIO = gpio.INVALID // I2S_SCK, SPI1_CS0, PWM0 + J2_13 gpio.PinIO = gpio.INVALID // GPIO116 + J2_14 = pin.GROUND // + J2_15 gpio.PinIO = gpio.INVALID // GPIO115 + J2_16 gpio.PinIO = gpio.INVALID // GPIO104 + J2_17 = pin.V3_3 // + J2_18 gpio.PinIO = gpio.INVALID // GPIO102 + J2_19 gpio.PinIO = gpio.INVALID // SPI0_MOSI + J2_20 = pin.GROUND // + J2_21 gpio.PinIO = gpio.INVALID // SPI0_MISO + J2_22 gpio.PinIO = gpio.INVALID // GPIO103 + J2_23 gpio.PinIO = gpio.INVALID // SPI0_CLK + J2_24 gpio.PinIO = gpio.INVALID // SPI0_CS0 + J2_25 = pin.GROUND // + J2_26 gpio.PinIO = gpio.INVALID // SPI0_CS1 + J2_27 gpio.PinIO = gpio.INVALID // I2C0_SDA + J2_28 gpio.PinIO = gpio.INVALID // I2C0_SCL + J2_29 gpio.PinIO = gpio.INVALID // CLK1 + J2_30 = pin.GROUND // + J2_31 gpio.PinIO = gpio.INVALID // CLK2 + J2_32 gpio.PinIO = gpio.INVALID // PWM0 + J2_33 gpio.PinIO = gpio.INVALID // PWM1 + J2_34 = pin.GROUND // + J2_35 gpio.PinIO = gpio.INVALID // I2S_WS, SPI1_MISO, PWM1 + J2_36 gpio.PinIO = gpio.INVALID // UART0_CTS, SPI1_CS2, UART1_CTS + J2_37 = pin.INVALID // BUG(tve): make pins J2_37 and J2_40 functional once analog support is implemented + J2_38 = pin.V1_8 // + J2_39 = pin.GROUND // + J2_40 = pin.INVALID // See above. +) + +// Present returns true if running on a Hardkernel ODROID-C0/C1/C1+ board. +// +// It looks for "8726_M8B" in the device tree or "ODROIDC" in cpuinfo. The +// following information is expected in the device dtree: +// root@odroid:/proc/device-tree# od -c compatible +// 0000000 A M L O G I C , 8 7 2 6 _ M 8 B +func Present() bool { + for _, c := range distro.DTCompatible() { + if strings.Contains(c, "8726_M8B") { + return true + } + } + return distro.CPUInfo()["Hardware"] == "ODROIDC" +} + +// + +// aliases is a list of aliases for the various gpio pins, this allows users to +// refer to pins using the documented and labeled names instead of some GPIOnnn +// name. The map key is the alias and the value is the real pin name. +var aliases = map[string]int{ + "I2C0_SDA": 74, + "I2C0_SCL": 75, + "I2C1_SDA": 76, + "I2C1_SCL": 77, + "I2CA_SDA": 74, + "I2CA_SCL": 75, + "I2CB_SDA": 76, + "I2CB_SCL": 77, + "SPI0_MOSI": 107, + "SPI0_MISO": 106, + "SPI0_CLK": 105, + "SPI0_CS0": 117, +} + +// sysfsPin is a safe way to get a sysfs pin +func sysfsPin(n int) gpio.PinIO { + if p, ok := sysfs.Pins[n]; ok { + return p + } + return gpio.INVALID +} + +// driver implements drivers.Driver. +type driver struct { +} + +func (d *driver) String() string { + return "odroid-c1" +} + +func (d *driver) Prerequisites() []string { + return nil +} + +func (d *driver) After() []string { + return []string{"sysfs-gpio"} +} + +func (d *driver) Init() (bool, error) { + if !Present() { + return false, errors.New("Hardkernel ODROID-C0/C1/C1+ board not detected") + } + J2_3 = sysfsPin(74) + J2_5 = sysfsPin(75) + J2_7 = sysfsPin(83) // usually taken by 1-wire driver + J2_8 = sysfsPin(113) // usually not available + J2_10 = sysfsPin(114) // usually not available + J2_11 = sysfsPin(88) + J2_12 = sysfsPin(87) + J2_13 = sysfsPin(116) + J2_15 = sysfsPin(115) + J2_16 = sysfsPin(104) + J2_18 = sysfsPin(102) + J2_19 = sysfsPin(107) + J2_21 = sysfsPin(106) + J2_22 = sysfsPin(103) + J2_23 = sysfsPin(105) + J2_24 = sysfsPin(117) + J2_26 = sysfsPin(118) + J2_27 = sysfsPin(76) + J2_28 = sysfsPin(77) + J2_29 = sysfsPin(101) + J2_31 = sysfsPin(100) + J2_32 = sysfsPin(99) + J2_33 = sysfsPin(108) + J2_35 = sysfsPin(97) + J2_36 = sysfsPin(98) + + // J2 is the 40-pin rPi-compatible header + J2 := [][]pin.Pin{ + {J2_1, J2_2}, + {J2_3, J2_4}, + {J2_5, J2_6}, + {J2_7, J2_8}, + {J2_9, J2_10}, + {J2_11, J2_12}, + {J2_13, J2_14}, + {J2_15, J2_16}, + {J2_17, J2_18}, + {J2_19, J2_20}, + {J2_21, J2_22}, + {J2_23, J2_24}, + {J2_25, J2_26}, + {J2_27, J2_28}, + {J2_29, J2_30}, + {J2_31, J2_32}, + {J2_33, J2_34}, + {J2_35, J2_36}, + {J2_37, J2_38}, + {J2_39, J2_40}, + } + if err := pinreg.Register("J2", J2); err != nil { + return true, err + } + for alias, number := range aliases { + if err := gpioreg.RegisterAlias(alias, strconv.Itoa(number)); err != nil { + return true, err + } + } + return true, nil +} + +func init() { + if isArm { + periph.MustRegister(&drv) + } +} + +var drv driver diff --git a/vendor/periph.io/x/periph/host/odroidc1/odroidc1_arm.go b/vendor/periph.io/x/periph/host/odroidc1/odroidc1_arm.go new file mode 100644 index 0000000..604d3f8 --- /dev/null +++ b/vendor/periph.io/x/periph/host/odroidc1/odroidc1_arm.go @@ -0,0 +1,7 @@ +// Copyright 2016 The PIO 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 odroidc1 + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/odroidc1/odroidc1_other.go b/vendor/periph.io/x/periph/host/odroidc1/odroidc1_other.go new file mode 100644 index 0000000..8b57cde --- /dev/null +++ b/vendor/periph.io/x/periph/host/odroidc1/odroidc1_other.go @@ -0,0 +1,9 @@ +// Copyright 2016 The PIO 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. + +// +build !arm + +package odroidc1 + +const isArm = false diff --git a/vendor/periph.io/x/periph/host/pine64/doc.go b/vendor/periph.io/x/periph/host/pine64/doc.go new file mode 100644 index 0000000..251aa2b --- /dev/null +++ b/vendor/periph.io/x/periph/host/pine64/doc.go @@ -0,0 +1,15 @@ +// Copyright 2016 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 pine64 contains Pine64 hardware logic. It is intrinsically +// related to package a64. +// +// Requires Armbian Jessie Server. +// +// Physical +// +// http://files.pine64.org/doc/Pine%20A64%20Schematic/Pine%20A64%20Pin%20Assignment%20160119.pdf +// +// http://wiki.pine64.org/images/2/2e/Pine64_Board_Connector_heatsink.png +package pine64 diff --git a/vendor/periph.io/x/periph/host/pine64/pine64.go b/vendor/periph.io/x/periph/host/pine64/pine64.go new file mode 100644 index 0000000..fb9051e --- /dev/null +++ b/vendor/periph.io/x/periph/host/pine64/pine64.go @@ -0,0 +1,272 @@ +// Copyright 2016 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 pine64 + +import ( + "errors" + "os" + + "periph.io/x/periph" + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/conn/pin/pinreg" + "periph.io/x/periph/host/allwinner" +) + +// Present returns true if running on a Pine64 board. +// +// https://www.pine64.org/ +func Present() bool { + if isArm { + // This is iffy at best. + _, err := os.Stat("/boot/pine64.dtb") + return err == nil + } + return false +} + +// Pine64 specific pins. +var ( + VCC = &pin.BasicPin{N: "VCC"} // + IOVCC = &pin.BasicPin{N: "IOVCC"} // Power supply for port A + TEMP_SENSOR = &pin.BasicPin{N: "TEMP_SENSOR"} // + IR_RX = &pin.BasicPin{N: "IR_RX"} // IR Data Receive + CHARGER_LED = &pin.BasicPin{N: "CHARGER_LED"} // + RESET = &pin.BasicPin{N: "RESET"} // + PWR_SWITCH = &pin.BasicPin{N: "PWR_SWITCH "} // +) + +// All the individual pins on the headers. +var ( + P1_1 = pin.V3_3 // max 40mA + P1_2 = pin.V5 // (filtered) + P1_3 = allwinner.PH3 // + P1_4 = pin.V5 // (filtered) + P1_5 = allwinner.PH2 // + P1_6 = pin.GROUND // + P1_7 = allwinner.PL10 // + P1_8 = allwinner.PB0 // + P1_9 = pin.GROUND // + P1_10 = allwinner.PB1 // + P1_11 = allwinner.PC7 // + P1_12 = allwinner.PC8 // + P1_13 = allwinner.PH9 // + P1_14 = pin.GROUND // + P1_15 = allwinner.PC12 // + P1_16 = allwinner.PC13 // + P1_17 = pin.V3_3 // + P1_18 = allwinner.PC14 // + P1_19 = allwinner.PC0 // + P1_20 = pin.GROUND // + P1_21 = allwinner.PC1 // + P1_22 = allwinner.PC15 // + P1_23 = allwinner.PC2 // + P1_24 = allwinner.PC3 // + P1_25 = pin.GROUND // + P1_26 = allwinner.PH7 // + P1_27 = allwinner.PL9 // + P1_28 = allwinner.PL8 // + P1_29 = allwinner.PH5 // + P1_30 = pin.GROUND // + P1_31 = allwinner.PH6 // + P1_32 = allwinner.PC4 // + P1_33 = allwinner.PC5 // + P1_34 = pin.GROUND // + P1_35 = allwinner.PC9 // + P1_36 = allwinner.PC6 // + P1_37 = allwinner.PC16 // + P1_38 = allwinner.PC10 // + P1_39 = pin.GROUND // + P1_40 = allwinner.PC11 // + + EULER_1 = pin.V3_3 // + EULER_2 = pin.DC_IN // + EULER_3 = pin.BAT_PLUS // + EULER_4 = pin.DC_IN // + EULER_5 = TEMP_SENSOR // + EULER_6 = pin.GROUND // + EULER_7 = IR_RX // + EULER_8 = pin.V5 // + EULER_9 = pin.GROUND // + EULER_10 = allwinner.PH8 // + EULER_11 = allwinner.PB3 // + EULER_12 = allwinner.PB4 // + EULER_13 = allwinner.PB5 // + EULER_14 = pin.GROUND // + EULER_15 = allwinner.PB6 // + EULER_16 = allwinner.PB7 // + EULER_17 = pin.V3_3 // + EULER_18 = allwinner.PD4 // + EULER_19 = allwinner.PD2 // + EULER_20 = pin.GROUND // + EULER_21 = allwinner.PD3 // + EULER_22 = allwinner.PD5 // + EULER_23 = allwinner.PD1 // + EULER_24 = allwinner.PD0 // + EULER_25 = pin.GROUND // + EULER_26 = allwinner.PD6 // + EULER_27 = allwinner.PB2 // + EULER_28 = allwinner.PD7 // + EULER_29 = allwinner.PB8 // + EULER_30 = allwinner.PB9 // + EULER_31 = allwinner.EAROUTP // + EULER_32 = allwinner.EAROUTN // + EULER_33 = pin.INVALID // + EULER_34 = pin.GROUND // + + EXP_1 = pin.V3_3 // + EXP_2 = allwinner.PL7 // + EXP_3 = CHARGER_LED // + EXP_4 = RESET // + EXP_5 = PWR_SWITCH // + EXP_6 = pin.GROUND // + EXP_7 = allwinner.PB8 // + EXP_8 = allwinner.PB9 // + EXP_9 = pin.GROUND // + EXP_10 = allwinner.KEY_ADC // + + WIFI_BT_1 = pin.GROUND // + WIFI_BT_2 = allwinner.PG6 // + WIFI_BT_3 = allwinner.PG0 // + WIFI_BT_4 = allwinner.PG7 // + WIFI_BT_5 = pin.GROUND // + WIFI_BT_6 = allwinner.PG8 // + WIFI_BT_7 = allwinner.PG1 // + WIFI_BT_8 = allwinner.PG9 // + WIFI_BT_9 = allwinner.PG2 // + WIFI_BT_10 = allwinner.PG10 // + WIFI_BT_11 = allwinner.PG3 // + WIFI_BT_12 = allwinner.PG11 // + WIFI_BT_13 = allwinner.PG4 // + WIFI_BT_14 = allwinner.PG12 // + WIFI_BT_15 = allwinner.PG5 // + WIFI_BT_16 = allwinner.PG13 // + WIFI_BT_17 = allwinner.PL2 // + WIFI_BT_18 = pin.GROUND // + WIFI_BT_19 = allwinner.PL3 // + WIFI_BT_20 = allwinner.PL5 // + WIFI_BT_21 = allwinner.X32KFOUT // + WIFI_BT_22 = allwinner.PL5 // + WIFI_BT_23 = pin.GROUND // + WIFI_BT_24 = allwinner.PL6 // + WIFI_BT_25 = VCC // + WIFI_BT_26 = IOVCC // + + AUDIO_LEFT = pin.INVALID // BUG(maruel): Fix once analog is implemented. + AUDIO_RIGHT = pin.INVALID // +) + +// + +// driver implements periph.Driver. +type driver struct { +} + +func (d *driver) String() string { + return "pine64" +} + +func (d *driver) Prerequisites() []string { + return nil +} + +func (d *driver) After() []string { + return []string{"allwinner-gpio", "allwinner-gpio-pl"} +} + +func (d *driver) Init() (bool, error) { + if !Present() { + return false, errors.New("pine64 board not detected") + } + if err := pinreg.Register("P1", [][]pin.Pin{ + {P1_1, P1_2}, + {P1_3, P1_4}, + {P1_5, P1_6}, + {P1_7, P1_8}, + {P1_9, P1_10}, + {P1_11, P1_12}, + {P1_13, P1_14}, + {P1_15, P1_16}, + {P1_17, P1_18}, + {P1_19, P1_20}, + {P1_21, P1_22}, + {P1_23, P1_24}, + {P1_25, P1_26}, + {P1_27, P1_28}, + {P1_29, P1_30}, + {P1_31, P1_32}, + {P1_33, P1_34}, + {P1_35, P1_36}, + {P1_37, P1_38}, + {P1_39, P1_40}, + }); err != nil { + return true, err + } + if err := pinreg.Register("EULER", [][]pin.Pin{ + {EULER_1, EULER_2}, + {EULER_3, EULER_4}, + {EULER_5, EULER_6}, + {EULER_7, EULER_8}, + {EULER_9, EULER_10}, + {EULER_11, EULER_12}, + {EULER_13, EULER_14}, + {EULER_15, EULER_16}, + {EULER_17, EULER_18}, + {EULER_19, EULER_20}, + {EULER_21, EULER_22}, + {EULER_23, EULER_24}, + {EULER_25, EULER_26}, + {EULER_27, EULER_28}, + {EULER_29, EULER_30}, + {EULER_31, EULER_32}, + {EULER_33, EULER_34}, + }); err != nil { + return true, err + } + + if err := pinreg.Register("EXP", [][]pin.Pin{ + {EXP_1, EXP_2}, + {EXP_3, EXP_4}, + {EXP_5, EXP_6}, + {EXP_7, EXP_8}, + {EXP_9, EXP_10}, + }); err != nil { + return true, err + } + + if err := pinreg.Register("WIFI_BT", [][]pin.Pin{ + {WIFI_BT_1, WIFI_BT_2}, + {WIFI_BT_3, WIFI_BT_4}, + {WIFI_BT_5, WIFI_BT_6}, + {WIFI_BT_7, WIFI_BT_8}, + {WIFI_BT_9, WIFI_BT_10}, + {WIFI_BT_11, WIFI_BT_12}, + {WIFI_BT_13, WIFI_BT_14}, + {WIFI_BT_15, WIFI_BT_16}, + {WIFI_BT_17, WIFI_BT_18}, + {WIFI_BT_19, WIFI_BT_20}, + {WIFI_BT_21, WIFI_BT_22}, + {WIFI_BT_23, WIFI_BT_24}, + {WIFI_BT_25, WIFI_BT_26}, + }); err != nil { + return true, err + } + + if err := pinreg.Register("AUDIO", [][]pin.Pin{ + {AUDIO_LEFT}, + {AUDIO_RIGHT}, + }); err != nil { + return true, err + } + + return true, nil +} + +func init() { + if isArm { + periph.MustRegister(&drv) + } +} + +var drv driver diff --git a/vendor/periph.io/x/periph/host/pine64/pine64_arm.go b/vendor/periph.io/x/periph/host/pine64/pine64_arm.go new file mode 100644 index 0000000..dcf8013 --- /dev/null +++ b/vendor/periph.io/x/periph/host/pine64/pine64_arm.go @@ -0,0 +1,7 @@ +// Copyright 2016 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 pine64 + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/pine64/pine64_arm64.go b/vendor/periph.io/x/periph/host/pine64/pine64_arm64.go new file mode 100644 index 0000000..01a13cd --- /dev/null +++ b/vendor/periph.io/x/periph/host/pine64/pine64_arm64.go @@ -0,0 +1,9 @@ +// Copyright 2016 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. + +// +build arm64 + +package pine64 + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/pine64/pine64_other.go b/vendor/periph.io/x/periph/host/pine64/pine64_other.go new file mode 100644 index 0000000..8c99603 --- /dev/null +++ b/vendor/periph.io/x/periph/host/pine64/pine64_other.go @@ -0,0 +1,9 @@ +// Copyright 2016 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. + +// +build !arm,!arm64 + +package pine64 + +const isArm = false diff --git a/vendor/periph.io/x/periph/host/pmem/alloc.go b/vendor/periph.io/x/periph/host/pmem/alloc.go new file mode 100644 index 0000000..78ebfb0 --- /dev/null +++ b/vendor/periph.io/x/periph/host/pmem/alloc.go @@ -0,0 +1,182 @@ +// Copyright 2016 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 pmem + +import ( + "bytes" + "io" + "io/ioutil" + "reflect" + "sync" + "unsafe" +) + +const pageSize = 4096 + +// Mem represents a section of memory that is usable by the DMA controller. +// +// Since this is physically allocated memory, that could potentially have been +// allocated in spite of OS consent, for example by asking the GPU directly, it +// is important to call Close() before process exit. +type Mem interface { + io.Closer + // Bytes returns the user space memory mapped buffer address as a slice of + // bytes. + // + // It is the raw view of the memory from this process. + Bytes() []byte + // AsPOD initializes a pointer to a POD (plain old data) to point to the + // memory mapped region. + // + // pp must be a pointer to: + // + // - pointer to a base size type (uint8, int64, float32, etc) + // - struct + // - array of the above + // - slice of the above + // + // and the value must be nil. Returns an error otherwise. + // + // If a pointer to a slice is passed in, it is initialized to the length and + // capacity set to the maximum number of elements this slice can represent. + // + // The pointer initialized points to the same address as Bytes(). + AsPOD(pp interface{}) error + // PhysAddr is the physical address. It can be either 32 bits or 64 bits, + // depending on the bitness of the OS kernel, not on the user mode build, + // e.g. you could have compiled on a 32 bits Go toolchain but running on a + // 64 bits kernel. + PhysAddr() uint64 +} + +// MemAlloc represents contiguous physically locked memory that was allocated. +// +// The memory is mapped in user space. +// +// MemAlloc implements Mem. +type MemAlloc struct { + View +} + +// Close unmaps the physical memory allocation. +func (m *MemAlloc) Close() error { + if err := munlock(m.orig); err != nil { + return err + } + return munmap(m.orig) +} + +// Alloc allocates a continuous chunk of physical memory. +// +// Size must be rounded to 4Kb. Allocations of 4Kb will normally succeed. +// Allocations larger than 64Kb will likely fail due to kernel memory +// fragmentation; rebooting the host or reducing the number of running programs +// may help. +// +// The allocated memory is uncached. +func Alloc(size int) (*MemAlloc, error) { + if size == 0 || size&(pageSize-1) != 0 { + return nil, wrapf("allocated memory must be rounded to %d bytes", pageSize) + } + if isLinux && !isWSL() { + return allocLinux(size) + } + return nil, wrapf("memory allocation is not supported on this platform") +} + +// + +var ( + wslOnce sync.Once + isWSLValue bool +) + +// uallocMemLocked allocates user space memory and requests the OS to have the +// chunk to be locked into physical memory. +func uallocMemLocked(size int) ([]byte, error) { + // It is important to write to the memory so it is forced to be present. + b, err := uallocMem(size) + if err == nil { + for i := range b { + b[i] = 0 + } + if err := mlock(b); err != nil { + // Ignore the unmap error. + _ = munmap(b) + return nil, wrapf("locking %d bytes failed: %v", size, err) + } + } + return b, err +} + +// allocLinux allocates physical memory and returns a user view to it. +func allocLinux(size int) (*MemAlloc, error) { + // TODO(maruel): Implement the "shotgun approach". Allocate a ton of 4Kb + // pages and lock them. Then look at their physical pages and only keep the + // one useful. Then create a linear mapping in memory to simplify the user + // mode with a single linear user space virtual address but keep the + // individual page alive with their initial allocation. When done release + // each individual page. + if size > pageSize { + return nil, wrapf("large allocation is not yet implemented") + } + // First allocate a chunk of user space memory. + b, err := uallocMemLocked(size) + if err != nil { + return nil, err + } + pages := make([]uint64, (size+pageSize-1)/pageSize) + // Figure out the physical memory addresses. + for i := range pages { + pages[i], err = virtToPhys(toRaw(b[pageSize*i:])) + if err != nil { + return nil, err + } + if pages[i] == 0 { + return nil, wrapf("failed to read page %d", i) + } + } + for i := 1; i < len(pages); i++ { + // Fail if the memory is not contiguous. + if pages[i] != pages[i-1]+pageSize { + return nil, wrapf("failed to allocate %d bytes of continugous physical memory; page %d =0x%x; page %d=0x%x", size, i, pages[i], i-1, pages[i-1]) + } + } + + return &MemAlloc{View{Slice: b, phys: pages[0], orig: b}}, nil +} + +// virtToPhys returns the physical memory address backing a virtual +// memory address. +func virtToPhys(virt uintptr) (uint64, error) { + physPage, err := ReadPageMap(virt) + if err != nil { + return 0, err + } + if physPage&(1<<63) == 0 { + // If high bit is not set, the page doesn't exist. + return 0, wrapf("0x%08x has no physical address", virt) + } + // Strip flags. See linux documentation on kernel.org for more details. + physPage &^= 0x1FF << 55 + return physPage * pageSize, nil +} + +func toRaw(b []byte) uintptr { + header := *(*reflect.SliceHeader)(unsafe.Pointer(&b)) + return header.Data +} + +// isWSL returns true if running under Windows Subsystem for Linux. +func isWSL() bool { + wslOnce.Do(func() { + if c, err := ioutil.ReadFile("/proc/sys/kernel/osrelease"); err == nil { + isWSLValue = bytes.Contains(c, []byte("Microsoft")) + } + }) + return isWSLValue +} + +var _ Mem = &MemAlloc{} diff --git a/vendor/periph.io/x/periph/host/pmem/doc.go b/vendor/periph.io/x/periph/host/pmem/doc.go new file mode 100644 index 0000000..b16c664 --- /dev/null +++ b/vendor/periph.io/x/periph/host/pmem/doc.go @@ -0,0 +1,69 @@ +// Copyright 2016 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 pmem implements handling of physical memory for user space programs. +// +// To make things confusing, a modern computer has many view of the memory +// (address spaces): +// +// User +// +// User mode address space is the virtual address space that an application +// runs in. It is generally a tad less than half the addressable memory, so on +// a 32 bits system, the addressable range is 1.9Gb. For 64 bits OS, it depends +// but it usually at least 3.5Gb. The memory is virtual and can be flushed to +// disk in the swap file unless individual pages are locked. +// +// Kernel +// +// Kernel address space is the virtual address space the kernel sees. It often +// can see the currently active user space program on the current CPU core in +// addition to all the memory the kernel sees. The kernel memory pages that are +// not mlock()'ed are 'virtual' and can be flushed to disk in the swap file +// when there's not enough RAM available. On linux systems, the kernel +// addressed memory can be mapped in user space via `/dev/kmem`. +// +// Physical +// +// Physical memory address space is the actual address of each page in the DRAM +// chip and anything connected to the memory controller. The mapping may be +// different depending on what controller looks at the bus, like with IOMMU. So +// a peripheral (GPU, DMA controller) may have a different view of the physical +// memory than the host CPU. On linux systems, this memory can be mapped in +// user space via `/dev/mem`. +// +// CPU +// +// The CPU or its subsystems may memory map registers (for example, to control +// GPIO pins, clock speed, etc). This is not "real" memory, this is a view of +// registers but it still follows "mostly" the same semantic as DRAM backed +// physical memory. +// +// Some CPU memory may have very special semantic where the mere fact of +// reading has side effects. For example reading a specific register may +// latches another. +// +// CPU memory accesses are layered with multiple caches, usually named L1, L2 +// and optionally L3. Some controllers (DMA) can see some cache levels (L2) but +// not others (L1) on some CPU architecture (bcm283x). This means that a user +// space program writing data to a memory page and immediately asking the DMA +// controller to read it may cause stale data to be read! +// +// Hypervisor +// +// Hypervisor can change the complete memory mapping as seen by the kernel. +// This is outside the scope of this project. :) +// +// Summary +// +// In practice, the semantics change between CPU manufacturers (Broadcom vs +// Allwinner) and between architectures (ARM vs x86). The most tricky one is to +// understand cached memory and how it affects coherence and performance. +// Uncached memory is extremely slow so it must only be used when necessary. +// +// References +// +// Overview of IOMMU: +// https://en.wikipedia.org/wiki/Input-output_memory_management_unit +package pmem diff --git a/vendor/periph.io/x/periph/host/pmem/mem_linux.go b/vendor/periph.io/x/periph/host/pmem/mem_linux.go new file mode 100644 index 0000000..fca165c --- /dev/null +++ b/vendor/periph.io/x/periph/host/pmem/mem_linux.go @@ -0,0 +1,56 @@ +// Copyright 2016 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 pmem + +import "syscall" + +const isLinux = true + +func mmap(fd uintptr, offset int64, length int) ([]byte, error) { + v, err := syscall.Mmap(int(fd), offset, length, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) + if err != nil { + return nil, wrapf("failed to memory map: %v", err) + } + return v, nil +} + +func munmap(b []byte) error { + if err := syscall.Munmap(b); err != nil { + return wrapf("failed to unmap memory: %v", err) + } + return nil + +} + +func mlock(b []byte) error { + if err := syscall.Mlock(b); err != nil { + return wrapf("failed to lock memory: %v", err) + } + return nil +} + +func munlock(b []byte) error { + if err := syscall.Munlock(b); err != nil { + return wrapf("failed to unlock memory: %v", err) + } + return nil +} + +// uallocMem allocates user space memory. +func uallocMem(size int) ([]byte, error) { + b, err := syscall.Mmap( + 0, + 0, + size, + syscall.PROT_READ|syscall.PROT_WRITE, + syscall.MAP_ANONYMOUS|syscall.MAP_LOCKED|syscall.MAP_NORESERVE|syscall.MAP_SHARED) + // syscall.MAP_HUGETLB / MAP_HUGE_2MB + // See /sys/kernel/mm/hugepages but both C.H.I.P. running Jessie and Raspbian + // Jessie do not expose huge pages. :( + if err != nil { + return nil, wrapf("allocating %d bytes failed: %v", size, err) + } + return b, err +} diff --git a/vendor/periph.io/x/periph/host/pmem/mem_other.go b/vendor/periph.io/x/periph/host/pmem/mem_other.go new file mode 100644 index 0000000..ce78a85 --- /dev/null +++ b/vendor/periph.io/x/periph/host/pmem/mem_other.go @@ -0,0 +1,30 @@ +// Copyright 2016 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. + +// +build !linux + +package pmem + +const isLinux = false + +func mmap(fd uintptr, offset int64, length int) ([]byte, error) { + return nil, wrapf("syscall.Mmap() not implemented on this OS") +} + +func munmap(b []byte) error { + return wrapf("syscall.Munmap() not implemented on this OS") +} + +func mlock(b []byte) error { + return wrapf("syscall.Mlock() not implemented on this OS") +} + +func munlock(b []byte) error { + return wrapf("syscall.Munlock() not implemented on this OS") +} + +// uallocMem allocates user space memory. +func uallocMem(size int) ([]byte, error) { + return make([]byte, size), nil +} diff --git a/vendor/periph.io/x/periph/host/pmem/pagemap.go b/vendor/periph.io/x/periph/host/pmem/pagemap.go new file mode 100644 index 0000000..362cf49 --- /dev/null +++ b/vendor/periph.io/x/periph/host/pmem/pagemap.go @@ -0,0 +1,64 @@ +// Copyright 2016 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 pmem + +import ( + "encoding/binary" + "errors" + "fmt" + "os" +) + +// ReadPageMap reads a physical address mapping for a virtual page address from +// /proc/self/pagemap. +// +// It returns the physical address that corresponds to the start of the virtual +// page within which the virtual address virtAddr is located. +// +// The meaning of the return value is documented at +// https://www.kernel.org/doc/Documentation/vm/pagemap.txt +func ReadPageMap(virtAddr uintptr) (uint64, error) { + if !isLinux || isWSL() { + return 0, errors.New("pmem: pagemap is not supported on this platform") + } + return readPageMapLinux(virtAddr) +} + +// + +var ( + pageMap fileIO + pageMapErr error +) + +func readPageMapLinux(virtAddr uintptr) (uint64, error) { + var b [8]byte + mu.Lock() + defer mu.Unlock() + if pageMap == nil && pageMapErr == nil { + // Open /proc/self/pagemap. + // + // It is a uint64 array where the index represents the virtual 4Kb page + // number and the value represents the physical page properties backing + // this virtual page. + pageMap, pageMapErr = openFile("/proc/self/pagemap", os.O_RDONLY|os.O_SYNC) + } + if pageMapErr != nil { + return 0, pageMapErr + } + // Convert address to page number, then index in uint64 array. + offset := int64(virtAddr / pageSize * 8) + if _, err := pageMap.Seek(offset, os.SEEK_SET); err != nil { + return 0, fmt.Errorf("pmem: failed to seek at 0x%x for 0x%x: %v", offset, virtAddr, err) + } + n, err := pageMap.Read(b[:]) + if err != nil { + return 0, fmt.Errorf("pmem: failed to read at 0x%x for 0x%x: %v", offset, virtAddr, err) + } + if n != len(b) { + return 0, fmt.Errorf("pmem: failed to read the amount of data %d", len(b)) + } + return binary.LittleEndian.Uint64(b[:]), nil +} diff --git a/vendor/periph.io/x/periph/host/pmem/smoketest.go b/vendor/periph.io/x/periph/host/pmem/smoketest.go new file mode 100644 index 0000000..7e184af --- /dev/null +++ b/vendor/periph.io/x/periph/host/pmem/smoketest.go @@ -0,0 +1,89 @@ +// Copyright 2016 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 pmem + +import ( + "bytes" + "math/rand" +) + +// TestCopy is used by CPU drivers to verify that the DMA engine works +// correctly. +// +// It is not meant to be used by end users. +// +// TestCopy allocates two buffer via `alloc`, once as the source and one as the +// destination. It fills the source with random data and the destination with +// 0x11. +// +// `copyMem` is expected to copy the memory from pSrc to pDst, with an offset +// of `hole` and size `size-2*hole`. +// +// The function `copyMem` being tested is only given the buffer physical +// addresses and must copy the data without other help. It is expected to +// +// This confirm misaligned DMA copying works. +// leverage the host's DMA engine. +func TestCopy(size, holeSize int, alloc func(size int) (Mem, error), copyMem func(pDst, pSrc uint64) error) error { + pSrc, err2 := alloc(size) + if err2 != nil { + return err2 + } + defer pSrc.Close() + pDst, err2 := alloc(size) + if err2 != nil { + return err2 + } + defer pDst.Close() + dst := pDst.Bytes() + for i := range dst { + dst[i] = 0x11 + } + src := make([]byte, size) + for i := range src { + src[i] = byte(rand.Int31()) + } + copy(pSrc.Bytes(), src[:]) + + // Run the driver supplied memory copying code. + if err := copyMem(pDst.PhysAddr(), pSrc.PhysAddr()); err != nil { + return err + } + + // Verifications. + for i := 0; i < holeSize; i++ { + if dst[i] != 0x11 { + return wrapf("DMA corrupted the buffer header: %x", dst[:holeSize]) + } + if dst[size-1-i] != 0x11 { + return wrapf("DMA corrupted the buffer footer: %x", dst[size-1-holeSize:]) + } + } + + // Headers and footers were not corupted in the destination. Verify the inner + // view that should match. + x := src[:size-2*holeSize] + y := dst[holeSize : size-holeSize] + if !bytes.Equal(x, y) { + offset := 0 + for len(x) != 0 && x[0] == y[0] { + x = x[1:] + y = y[1:] + offset++ + } + for len(x) != 0 && x[len(x)-1] == y[len(y)-1] { + x = x[:len(x)-1] + y = y[:len(y)-1] + } + if len(x) > 32 { + x = x[:32] + } + if len(y) > 32 { + y = y[:32] + } + return wrapf("DMA corrupted the buffer at offset %d:\n%x\n%x", offset, x, y) + } + return nil +} diff --git a/vendor/periph.io/x/periph/host/pmem/view.go b/vendor/periph.io/x/periph/host/pmem/view.go new file mode 100644 index 0000000..6d207ed --- /dev/null +++ b/vendor/periph.io/x/periph/host/pmem/view.go @@ -0,0 +1,283 @@ +// Copyright 2016 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 pmem + +import ( + "fmt" + "io" + "os" + "reflect" + "sync" + "unsafe" + + "periph.io/x/periph/host/fs" +) + +// Slice can be transparently viewed as []byte, []uint32 or a struct. +type Slice []byte + +// Uint32 returns a view of the byte slice as a []uint32. +func (s *Slice) Uint32() []uint32 { + header := *(*reflect.SliceHeader)(unsafe.Pointer(s)) + header.Len /= 4 + header.Cap /= 4 + return *(*[]uint32)(unsafe.Pointer(&header)) +} + +// Bytes implements Mem. +func (s *Slice) Bytes() []byte { + return *s +} + +// AsPOD implements Mem. +func (s *Slice) AsPOD(pp interface{}) error { + if pp == nil { + return wrapf("require Ptr, got nil") + } + vpp := reflect.ValueOf(pp) + if elemSize, err := isPS(len(*s), vpp); err == nil { + p := vpp.Elem() + t := p.Type().Elem() + if elemSize > len(*s) { + return wrapf("can't map slice of struct %s (size %d) on [%d]byte", t, elemSize, len(*s)) + } + nbElems := len(*s) / elemSize + // Use casting black magic to set the internal slice headers. + hdr := (*reflect.SliceHeader)(unsafe.Pointer(p.UnsafeAddr())) + hdr.Data = ((*reflect.SliceHeader)(unsafe.Pointer(s))).Data + hdr.Len = nbElems + hdr.Cap = nbElems + return nil + } + + size, err := isPP(vpp) + if err != nil { + return err + } + p := vpp.Elem() + t := p.Type().Elem() + if size > len(*s) { + return wrapf("can't map struct %s (size %d) on [%d]byte", t, size, len(*s)) + } + // Use casting black magic to read the internal slice headers. + dest := unsafe.Pointer(((*reflect.SliceHeader)(unsafe.Pointer(s))).Data) + // Use reflection black magic to write to the original pointer. + p.Set(reflect.NewAt(t, dest)) + return nil +} + +// View represents a view of physical memory memory mapped into user space. +// +// It is usually used to map CPU registers into user space, usually I/O +// registers and the likes. +// +// It is not required to call Close(), the kernel will clean up on process +// shutdown. +type View struct { + Slice + orig []uint8 // Reference rounded to the lowest 4Kb page containing Slice. + phys uint64 // physical address of the base of Slice. +} + +// Close unmaps the memory from the user address space. +// +// This is done naturally by the OS on process teardown (when the process +// exits) so this is not a hard requirement to call this function. +func (v *View) Close() error { + return munmap(v.orig) +} + +// PhysAddr implements Mem. +func (v *View) PhysAddr() uint64 { + return v.phys +} + +// MapGPIO returns a CPU specific memory mapping of the CPU I/O registers using +// /dev/gpiomem. +// +// At the moment, /dev/gpiomem is only supported on Raspbian Jessie via a +// specific kernel driver. +func MapGPIO() (*View, error) { + if isLinux { + return mapGPIOLinux() + } + return nil, wrapf("/dev/gpiomem is not supported on this platform") +} + +// Map returns a memory mapped view of arbitrary physical memory range using OS +// provided functionality. +// +// Maps size of memory, rounded on a 4kb window. +// +// This function is dangerous and should be used wisely. It normally requires +// super privileges (root). On Linux, it leverages /dev/mem. +func Map(base uint64, size int) (*View, error) { + if isLinux { + return mapLinux(base, size) + } + return nil, wrapf("physical memory mapping is not supported on this platform") +} + +// MapAsPOD is a leaky shorthand of calling Map(base, sizeof(v)) then AsPOD(v). +// +// There is no way to reclaim the memory map. +// +// A slice cannot be used, as it does not have inherent size. Use an aray +// instead. +func MapAsPOD(base uint64, i interface{}) error { + // Automatically determine the necessary size. Because of this, slice of + // unspecified length cannot be used here. + if i == nil { + return wrapf("require Ptr, got nil") + } + v := reflect.ValueOf(i) + size, err := isPP(v) + if err != nil { + return err + } + m, err := Map(base, size) + if err != nil { + return err + } + return m.AsPOD(i) +} + +// + +// Keep a cache of open file handles instead of opening and closing repeatedly. +var ( + mu sync.Mutex + gpioMemErr error + gpioMemView *View + devMem fileIO + devMemErr error + openFile = openFileOrig +) + +type fileIO interface { + io.Closer + io.Seeker + io.Reader + Fd() uintptr +} + +func openFileOrig(path string, flag int) (fileIO, error) { + f, err := fs.Open(path, flag) + if err != nil { + return nil, err + } + return f, nil +} + +// mapGPIOLinux is purely Raspbian specific. +func mapGPIOLinux() (*View, error) { + mu.Lock() + defer mu.Unlock() + if gpioMemView == nil && gpioMemErr == nil { + if f, err := openFile("/dev/gpiomem", os.O_RDWR|os.O_SYNC); err == nil { + defer f.Close() + if i, err := mmap(f.Fd(), 0, pageSize); err == nil { + gpioMemView = &View{Slice: i, orig: i, phys: 0} + } else { + gpioMemErr = wrapf("failed to memory map in user space GPIO memory: %v", err) + } + } else { + gpioMemErr = wrapf("failed to open GPIO memory: %v", err) + } + } + return gpioMemView, gpioMemErr +} + +// mapLinux leverages /dev/mem to map a view of physical memory. +func mapLinux(base uint64, size int) (*View, error) { + f, err := openDevMemLinux() + if err != nil { + return nil, err + } + // Align base and size at 4Kb. + offset := int(base & 0xFFF) + i, err := mmap(f.Fd(), int64(base&^0xFFF), (size+offset+0xFFF)&^0xFFF) + if err != nil { + return nil, wrapf("mapping at 0x%x failed: %v", base, err) + } + return &View{Slice: i[offset : offset+size], orig: i, phys: base + uint64(offset)}, nil +} + +func openDevMemLinux() (fileIO, error) { + mu.Lock() + defer mu.Unlock() + if devMem == nil && devMemErr == nil { + if devMem, devMemErr = openFile("/dev/mem", os.O_RDWR|os.O_SYNC); devMemErr != nil { + devMemErr = wrapf("failed to open physical memory: %v", devMemErr) + } + } + return devMem, devMemErr +} + +func isAcceptableInner(t reflect.Type) error { + switch k := t.Kind(); k { + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + return nil + case reflect.Array: + return isAcceptableInner(t.Elem()) + case reflect.Struct: + for i := 0; i < t.NumField(); i++ { + if err := isAcceptableInner(t.Field(i).Type); err != nil { + return err + } + } + return nil + default: + return wrapf("require Ptr to Ptr to a POD type, got Ptr to Ptr to %s", k) + } +} + +// isPP makes sure it is a pointer to a nil-pointer to something. It does +// sanity checks to reduce likelihood of a panic(). +func isPP(pp reflect.Value) (int, error) { + if k := pp.Kind(); k != reflect.Ptr { + return 0, wrapf("require Ptr, got %s of %s", k, pp.Type().Name()) + } + p := pp.Elem() + if k := p.Kind(); k != reflect.Ptr { + return 0, wrapf("require Ptr to Ptr, got %s", k) + } + if !p.IsNil() { + return 0, wrapf("require Ptr to Ptr to be nil") + } + // p.Elem() can't be used since it's a nil pointer. Use the type instead. + t := p.Type().Elem() + if err := isAcceptableInner(t); err != nil { + return 0, err + } + return int(t.Size()), nil +} + +// isPS makes sure it is a pointer to a nil-slice of something. It does +// sanity checks to reduce likelihood of a panic(). +func isPS(bufSize int, ps reflect.Value) (int, error) { + if k := ps.Kind(); k != reflect.Ptr { + return 0, wrapf("require Ptr, got %s of %s", k, ps.Type().Name()) + } + s := ps.Elem() + if k := s.Kind(); k != reflect.Slice { + return 0, wrapf("require Ptr to Slice, got %s", k) + } + if !s.IsNil() { + return 0, wrapf("require Ptr to Slice to be nil") + } + // s.Elem() can't be used since it's a nil slice. Use the type instead. + t := s.Type().Elem() + if err := isAcceptableInner(t); err != nil { + return 0, err + } + return int(t.Size()), nil +} + +func wrapf(format string, a ...interface{}) error { + return fmt.Errorf("pmem: "+format, a...) +} diff --git a/vendor/periph.io/x/periph/host/rpi/doc.go b/vendor/periph.io/x/periph/host/rpi/doc.go new file mode 100644 index 0000000..8730b9d --- /dev/null +++ b/vendor/periph.io/x/periph/host/rpi/doc.go @@ -0,0 +1,15 @@ +// Copyright 2016 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 rpi contains Raspberry Pi hardware logic. It is intrinsically +// related to package bcm283x. +// +// Assumes Raspbian but does not directly depend on the distro being Raspbian. +// Windows IoT is currently not supported. +// +// Physical +// +// The physical pin out is based on http://www.raspberrypi.org information but +// http://pinout.xyz/ has a nice interactive web page. +package rpi diff --git a/vendor/periph.io/x/periph/host/rpi/rpi.go b/vendor/periph.io/x/periph/host/rpi/rpi.go new file mode 100644 index 0000000..3cd4adb --- /dev/null +++ b/vendor/periph.io/x/periph/host/rpi/rpi.go @@ -0,0 +1,815 @@ +// Copyright 2016 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. + +// Raspberry Pi pin out. + +package rpi + +import ( + "errors" + "fmt" + "os" + "strconv" + + "periph.io/x/periph" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/conn/pin/pinreg" + "periph.io/x/periph/host/bcm283x" + "periph.io/x/periph/host/distro" +) + +// Present returns true if running on a Raspberry Pi board. +// +// https://www.raspberrypi.org/ +func Present() bool { + if isArm { + // This is iffy at best. + _, err := os.Stat("/sys/bus/platform/drivers/raspberrypi-firmware") + return err == nil + } + return false +} + +// Pin as connect on the 40 pins extension header. +// +// Schematics are useful to know what is connected to what: +// https://www.raspberrypi.org/documentation/hardware/raspberrypi/schematics/README.md +// +// The actual pin mapping depends on the board revision! The default values are +// set as the 40 pins header on Raspberry Pi 2 and Raspberry Pi 3. +// +// Some header info here: http://elinux.org/RPi_Low-level_peripherals +// +// P1 is also known as J8 on A+, B+, 2 and later. +var ( + // Raspberry Pi A and B, 26 pin header: + P1_1 pin.Pin = pin.V3_3 // max 30mA + P1_2 pin.Pin = pin.V5 // (filtered) + P1_3 gpio.PinIO = bcm283x.GPIO2 // High, I2C1_SDA + P1_4 pin.Pin = pin.V5 // + P1_5 gpio.PinIO = bcm283x.GPIO3 // High, I2C1_SCL + P1_6 pin.Pin = pin.GROUND // + P1_7 gpio.PinIO = bcm283x.GPIO4 // High, CLK0 + P1_8 gpio.PinIO = bcm283x.GPIO14 // Low, UART0_TX, UART1_TX + P1_9 pin.Pin = pin.GROUND // + P1_10 gpio.PinIO = bcm283x.GPIO15 // Low, UART0_RX, UART1_RX + P1_11 gpio.PinIO = bcm283x.GPIO17 // Low, UART0_RTS, SPI1_CS1, UART1_RTS + P1_12 gpio.PinIO = bcm283x.GPIO18 // Low, I2S_SCK, SPI1_CS0, PWM0 + P1_13 gpio.PinIO = bcm283x.GPIO27 // Low, + P1_14 pin.Pin = pin.GROUND // + P1_15 gpio.PinIO = bcm283x.GPIO22 // Low, + P1_16 gpio.PinIO = bcm283x.GPIO23 // Low, + P1_17 pin.Pin = pin.V3_3 // + P1_18 gpio.PinIO = bcm283x.GPIO24 // Low, + P1_19 gpio.PinIO = bcm283x.GPIO10 // Low, SPI0_MOSI + P1_20 pin.Pin = pin.GROUND // + P1_21 gpio.PinIO = bcm283x.GPIO9 // Low, SPI0_MISO + P1_22 gpio.PinIO = bcm283x.GPIO25 // Low, + P1_23 gpio.PinIO = bcm283x.GPIO11 // Low, SPI0_CLK + P1_24 gpio.PinIO = bcm283x.GPIO8 // High, SPI0_CS0 + P1_25 pin.Pin = pin.GROUND // + P1_26 gpio.PinIO = bcm283x.GPIO7 // High, SPI0_CS1 + + // Raspberry Pi A+, B+, 2 and later, 40 pin header (also named J8): + P1_27 gpio.PinIO = bcm283x.GPIO0 // High, I2C0_SDA used to probe for HAT EEPROM, see https://github.com/raspberrypi/hats + P1_28 gpio.PinIO = bcm283x.GPIO1 // High, I2C0_SCL + P1_29 gpio.PinIO = bcm283x.GPIO5 // High, CLK1 + P1_30 pin.Pin = pin.GROUND // + P1_31 gpio.PinIO = bcm283x.GPIO6 // High, CLK2 + P1_32 gpio.PinIO = bcm283x.GPIO12 // Low, PWM0 + P1_33 gpio.PinIO = bcm283x.GPIO13 // Low, PWM1 + P1_34 pin.Pin = pin.GROUND // + P1_35 gpio.PinIO = bcm283x.GPIO19 // Low, I2S_WS, SPI1_MISO, PWM1 + P1_36 gpio.PinIO = bcm283x.GPIO16 // Low, UART0_CTS, SPI1_CS2, UART1_CTS + P1_37 gpio.PinIO = bcm283x.GPIO26 // + P1_38 gpio.PinIO = bcm283x.GPIO20 // Low, I2S_DIN, SPI1_MOSI, CLK0 + P1_39 pin.Pin = pin.GROUND // + P1_40 gpio.PinIO = bcm283x.GPIO21 // Low, I2S_DOUT, SPI1_CLK, CLK1 + + // P5 header on Raspberry Pi A and B, PCB v2: + P5_1 pin.Pin = pin.V5 + P5_2 pin.Pin = pin.V3_3 + P5_3 gpio.PinIO = bcm283x.GPIO28 // Float, I2C0_SDA, I2S_SCK + P5_4 gpio.PinIO = bcm283x.GPIO29 // Float, I2C0_SCL, I2S_WS + P5_5 gpio.PinIO = bcm283x.GPIO30 // Low, I2S_DIN, UART0_CTS, UART1_CTS + P5_6 gpio.PinIO = bcm283x.GPIO31 // Low, I2S_DOUT, UART0_RTS, UART1_RTS + P5_7 pin.Pin = pin.GROUND + P5_8 pin.Pin = pin.GROUND + + AUDIO_RIGHT = bcm283x.GPIO40 // Low, PWM0, SPI2_MISO, UART1_TX + AUDIO_LEFT = bcm283x.GPIO41 // Low, PWM1, SPI2_MOSI, UART1_RX + HDMI_HOTPLUG_DETECT = bcm283x.GPIO46 // High, +) + +// Pin as connected on the SODIMM header. +// +// Documentation is https://www.raspberrypi.org/documentation/hardware/computemodule/datasheets/rpi_DATA_CM_1p0.pdf +// +// There are some differences for CM3-Lite and CM1. +var ( + SO_1 pin.Pin = pin.GROUND // GND + SO_2 pin.Pin = pin.INVALID // EMMC_DISABLE_N + SO_3 gpio.PinIO = bcm283x.GPIO0 // GPIO0 + SO_4 pin.Pin = pin.INVALID // NC, SDX_VDD, NC + SO_5 gpio.PinIO = bcm283x.GPIO1 // GPIO1 + SO_6 pin.Pin = pin.INVALID // NC, SDX_VDD, NC + SO_7 pin.Pin = pin.GROUND // GND + SO_8 pin.Pin = pin.GROUND // GND + SO_9 gpio.PinIO = bcm283x.GPIO2 // GPIO2 + SO_10 pin.Pin = pin.INVALID // NC, SDX_CLK, NC + SO_11 gpio.PinIO = bcm283x.GPIO3 // GPIO3 + SO_12 pin.Pin = pin.INVALID // NC, SDX_CMD, NC + SO_13 pin.Pin = pin.GROUND // GND + SO_14 pin.Pin = pin.GROUND // GND + SO_15 gpio.PinIO = bcm283x.GPIO4 // GPIO4 + SO_16 pin.Pin = pin.INVALID // NC, SDX_D0, NC + SO_17 gpio.PinIO = bcm283x.GPIO5 // GPIO5 + SO_18 pin.Pin = pin.INVALID // NC, SDX_D1, NC + SO_19 pin.Pin = pin.GROUND // GND + SO_20 pin.Pin = pin.GROUND // GND + SO_21 gpio.PinIO = bcm283x.GPIO6 // GPIO6 + SO_22 pin.Pin = pin.INVALID // NC, SDX_D2, NC + SO_23 gpio.PinIO = bcm283x.GPIO7 // GPIO7 + SO_24 pin.Pin = pin.INVALID // NC, SDX_D3, NC + SO_25 pin.Pin = pin.GROUND // GND + SO_26 pin.Pin = pin.GROUND // GND + SO_27 gpio.PinIO = bcm283x.GPIO8 // GPIO8 + SO_28 gpio.PinIO = bcm283x.GPIO28 // GPIO28 + SO_29 gpio.PinIO = bcm283x.GPIO9 // GPIO9 + SO_30 gpio.PinIO = bcm283x.GPIO29 // GPIO29 + SO_31 pin.Pin = pin.GROUND // GND + SO_32 pin.Pin = pin.GROUND // GND + SO_33 gpio.PinIO = bcm283x.GPIO10 // GPIO10 + SO_34 gpio.PinIO = bcm283x.GPIO30 // GPIO30 + SO_35 gpio.PinIO = bcm283x.GPIO11 // GPIO11 + SO_36 gpio.PinIO = bcm283x.GPIO31 // GPIO31 + SO_37 pin.Pin = pin.GROUND // GND + SO_38 pin.Pin = pin.GROUND // GND + SO_39 pin.Pin = pin.DC_IN // GPIO0-27_VDD + SO_40 pin.Pin = pin.DC_IN // GPIO0-27_VDD + SO_41 pin.Pin = pin.DC_IN // GPIO28-45_VDD + SO_42 pin.Pin = pin.DC_IN // GPIO28-45_VDD + SO_43 pin.Pin = pin.GROUND // GND + SO_44 pin.Pin = pin.GROUND // GND + SO_45 gpio.PinIO = bcm283x.GPIO12 // GPIO12 + SO_46 gpio.PinIO = bcm283x.GPIO32 // GPIO32 + SO_47 gpio.PinIO = bcm283x.GPIO13 // GPIO13 + SO_48 gpio.PinIO = bcm283x.GPIO33 // GPIO33 + SO_49 pin.Pin = pin.GROUND // GND + SO_50 pin.Pin = pin.GROUND // GND + SO_51 gpio.PinIO = bcm283x.GPIO14 // GPIO14 + SO_52 gpio.PinIO = bcm283x.GPIO34 // GPIO34 + SO_53 gpio.PinIO = bcm283x.GPIO15 // GPIO15 + SO_54 gpio.PinIO = bcm283x.GPIO35 // GPIO35 + SO_55 pin.Pin = pin.GROUND // GND + SO_56 pin.Pin = pin.GROUND // GND + SO_57 gpio.PinIO = bcm283x.GPIO16 // GPIO16 + SO_58 gpio.PinIO = bcm283x.GPIO36 // GPIO36 + SO_59 gpio.PinIO = bcm283x.GPIO17 // GPIO17 + SO_60 gpio.PinIO = bcm283x.GPIO37 // GPIO37 + SO_61 pin.Pin = pin.GROUND // GND + SO_62 pin.Pin = pin.GROUND // GND + SO_63 gpio.PinIO = bcm283x.GPIO18 // GPIO18 + SO_64 gpio.PinIO = bcm283x.GPIO38 // GPIO38 + SO_65 gpio.PinIO = bcm283x.GPIO19 // GPIO19 + SO_66 gpio.PinIO = bcm283x.GPIO39 // GPIO39 + SO_67 pin.Pin = pin.GROUND // GND + SO_68 pin.Pin = pin.GROUND // GND + SO_69 gpio.PinIO = bcm283x.GPIO20 // GPIO20 + SO_70 gpio.PinIO = bcm283x.GPIO40 // GPIO40 + SO_71 gpio.PinIO = bcm283x.GPIO21 // GPIO21 + SO_72 gpio.PinIO = bcm283x.GPIO41 // GPIO41 + SO_73 pin.Pin = pin.GROUND // GND + SO_74 pin.Pin = pin.GROUND // GND + SO_75 gpio.PinIO = bcm283x.GPIO22 // GPIO22 + SO_76 gpio.PinIO = bcm283x.GPIO42 // GPIO42 + SO_77 gpio.PinIO = bcm283x.GPIO23 // GPIO23 + SO_78 gpio.PinIO = bcm283x.GPIO43 // GPIO43 + SO_79 pin.Pin = pin.GROUND // GND + SO_80 pin.Pin = pin.GROUND // GND + SO_81 gpio.PinIO = bcm283x.GPIO24 // GPIO24 + SO_82 gpio.PinIO = bcm283x.GPIO44 // GPIO44 + SO_83 gpio.PinIO = bcm283x.GPIO25 // GPIO25 + SO_84 gpio.PinIO = bcm283x.GPIO45 // GPIO45 + SO_85 pin.Pin = pin.GROUND // GND + SO_86 pin.Pin = pin.GROUND // GND + SO_87 gpio.PinIO = bcm283x.GPIO26 // GPIO26 + SO_88 pin.Pin = pin.INVALID // HDMI_HPD_N_1V8, HDMI_HPD_N_1V8, GPIO46_1V8 + SO_89 gpio.PinIO = bcm283x.GPIO27 // GPIO27 + SO_90 pin.Pin = pin.INVALID // EMMC_EN_N_1V8, EMMC_EN_N_1V8, GPIO47_1V8 + SO_91 pin.Pin = pin.GROUND // GND + SO_92 pin.Pin = pin.GROUND // GND + SO_93 pin.Pin = pin.INVALID // DSI0_DN1 + SO_94 pin.Pin = pin.INVALID // DSI1_DP0 + SO_95 pin.Pin = pin.INVALID // DSI0_DP1 + SO_96 pin.Pin = pin.INVALID // DSI1_DN0 + SO_97 pin.Pin = pin.GROUND // GND + SO_98 pin.Pin = pin.GROUND // GND + SO_99 pin.Pin = pin.INVALID // DSI0_DN0 + SO_100 pin.Pin = pin.INVALID // DSI1_CP + SO_101 pin.Pin = pin.INVALID // DSI0_DP0 + SO_102 pin.Pin = pin.INVALID // DSI1_CN + SO_103 pin.Pin = pin.GROUND // GND + SO_104 pin.Pin = pin.GROUND // GND + SO_105 pin.Pin = pin.INVALID // DSI0_CN + SO_106 pin.Pin = pin.INVALID // DSI1_DP3 + SO_107 pin.Pin = pin.INVALID // DSI0_CP + SO_108 pin.Pin = pin.INVALID // DSI1_DN3 + SO_109 pin.Pin = pin.GROUND // GND + SO_110 pin.Pin = pin.GROUND // GND + SO_111 pin.Pin = pin.INVALID // HDMI_CLK_N + SO_112 pin.Pin = pin.INVALID // DSI1_DP2 + SO_113 pin.Pin = pin.INVALID // HDMI_CLK_P + SO_114 pin.Pin = pin.INVALID // DSI1_DN2 + SO_115 pin.Pin = pin.GROUND // GND + SO_116 pin.Pin = pin.GROUND // GND + SO_117 pin.Pin = pin.INVALID // HDMI_D0_N + SO_118 pin.Pin = pin.INVALID // DSI1_DP1 + SO_119 pin.Pin = pin.INVALID // HDMI_D0_P + SO_120 pin.Pin = pin.INVALID // DSI1_DN1 + SO_121 pin.Pin = pin.GROUND // GND + SO_122 pin.Pin = pin.GROUND // GND + SO_123 pin.Pin = pin.INVALID // HDMI_D1_N + SO_124 pin.Pin = pin.INVALID // NC + SO_125 pin.Pin = pin.INVALID // HDMI_D1_P + SO_126 pin.Pin = pin.INVALID // NC + SO_127 pin.Pin = pin.GROUND // GND + SO_128 pin.Pin = pin.INVALID // NC + SO_129 pin.Pin = pin.INVALID // HDMI_D2_N + SO_130 pin.Pin = pin.INVALID // NC + SO_131 pin.Pin = pin.INVALID // HDMI_D2_P + SO_132 pin.Pin = pin.INVALID // NC + SO_133 pin.Pin = pin.GROUND // GND + SO_134 pin.Pin = pin.GROUND // GND + SO_135 pin.Pin = pin.INVALID // CAM1_DP3 + SO_136 pin.Pin = pin.INVALID // CAM0_DP0 + SO_137 pin.Pin = pin.INVALID // CAM1_DN3 + SO_138 pin.Pin = pin.INVALID // CAM0_DN0 + SO_139 pin.Pin = pin.GROUND // GND + SO_140 pin.Pin = pin.GROUND // GND + SO_141 pin.Pin = pin.INVALID // CAM1_DP2 + SO_142 pin.Pin = pin.INVALID // CAM0_CP + SO_143 pin.Pin = pin.INVALID // CAM1_DN2 + SO_144 pin.Pin = pin.INVALID // CAM0_CN + SO_145 pin.Pin = pin.GROUND // GND + SO_146 pin.Pin = pin.GROUND // GND + SO_147 pin.Pin = pin.INVALID // CAM1_CP + SO_148 pin.Pin = pin.INVALID // CAM0_DP1 + SO_149 pin.Pin = pin.INVALID // CAM1_CN + SO_150 pin.Pin = pin.INVALID // CAM0_DN1 + SO_151 pin.Pin = pin.GROUND // GND + SO_152 pin.Pin = pin.GROUND // GND + SO_153 pin.Pin = pin.INVALID // CAM1_DP1 + SO_154 pin.Pin = pin.INVALID // NC + SO_155 pin.Pin = pin.INVALID // CAM1_DN1 + SO_156 pin.Pin = pin.INVALID // NC + SO_157 pin.Pin = pin.GROUND // GND + SO_158 pin.Pin = pin.INVALID // NC + SO_159 pin.Pin = pin.INVALID // CAM1_DP0 + SO_160 pin.Pin = pin.INVALID // NC + SO_161 pin.Pin = pin.INVALID // CAM1_DN0 + SO_162 pin.Pin = pin.INVALID // NC + SO_163 pin.Pin = pin.GROUND // GND + SO_164 pin.Pin = pin.GROUND // GND + SO_165 pin.Pin = pin.INVALID // USB_DP + SO_166 pin.Pin = pin.INVALID // TVDAC + SO_167 pin.Pin = pin.INVALID // USB_DM + SO_168 pin.Pin = pin.INVALID // USB_OTGID + SO_169 pin.Pin = pin.GROUND // GND + SO_170 pin.Pin = pin.GROUND // GND + SO_171 pin.Pin = pin.INVALID // HDMI_CEC + SO_172 pin.Pin = pin.INVALID // VC_TRST_N + SO_173 pin.Pin = pin.INVALID // HDMI_SDA + SO_174 pin.Pin = pin.INVALID // VC_TDI + SO_175 pin.Pin = pin.INVALID // HDMI_SCL + SO_176 pin.Pin = pin.INVALID // VC_TMS + SO_177 pin.Pin = pin.INVALID // RUN + SO_178 pin.Pin = pin.INVALID // VC_TDO + SO_179 pin.Pin = pin.INVALID // VDD_CORE (DO NOT CONNECT) + SO_180 pin.Pin = pin.INVALID // VC_TCK + SO_181 pin.Pin = pin.GROUND // GND + SO_182 pin.Pin = pin.GROUND // GND + SO_183 pin.Pin = pin.V1_8 // 1V8 + SO_184 pin.Pin = pin.V1_8 // 1V8 + SO_185 pin.Pin = pin.V1_8 // 1V8 + SO_186 pin.Pin = pin.V1_8 // 1V8 + SO_187 pin.Pin = pin.GROUND // GND + SO_188 pin.Pin = pin.GROUND // GND + SO_189 pin.Pin = pin.DC_IN // VDAC + SO_190 pin.Pin = pin.DC_IN // VDAC + SO_191 pin.Pin = pin.V3_3 // 3V3 + SO_192 pin.Pin = pin.V3_3 // 3V3 + SO_193 pin.Pin = pin.V3_3 // 3V3 + SO_194 pin.Pin = pin.V3_3 // 3V3 + SO_195 pin.Pin = pin.GROUND // GND + SO_196 pin.Pin = pin.GROUND // GND + SO_197 pin.Pin = pin.DC_IN // VBAT + SO_198 pin.Pin = pin.DC_IN // VBAT + SO_199 pin.Pin = pin.DC_IN // VBAT + SO_200 pin.Pin = pin.DC_IN // VBAT +) + +// + +// revisionCode processes the CPU revision code based on documentation at +// https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md +// +// Format is: uuuuuuuuFMMMCCCCPPPPTTTTTTTTRRRR +// 2 2 1 1 0 0 +// 3 0 6 2 4 0 +type revisionCode uint32 + +// parseRevision processes the old style revision codes to new style bitpacked +// format. +func parseRevision(v uint32) (revisionCode, error) { + w := revisionCode(v) & warrantyVoid + switch v &^ uint32(warrantyVoid) { + case 0x2, 0x3: + return w | newFormat | memory256MB | egoman | bcm2835 | board1B, nil + case 0x4: + return w | newFormat | memory256MB | sonyUK | bcm2835 | board1B | 2, nil // v2.0 + case 0x5: + return w | newFormat | memory256MB | bcm2835 | board1B | 2, nil // v2.0 Qisda + case 0x6: + return w | newFormat | memory256MB | egoman | bcm2835 | board1B | 2, nil // v2.0 + case 0x7: + return w | newFormat | memory256MB | egoman | bcm2835 | board1A | 2, nil // v2.0 + case 0x8: + return w | newFormat | memory256MB | sonyUK | bcm2835 | board1A | 2, nil // v2.0 + case 0x9: + return w | newFormat | memory256MB | bcm2835 | board1A | 2, nil // v2.0 Qisda + case 0xd: + return w | newFormat | memory512MB | egoman | bcm2835 | board1B | 2, nil // v2.0 + case 0xe: + return w | newFormat | memory512MB | sonyUK | bcm2835 | board1B | 2, nil // v2.0 + case 0xf: + return w | newFormat | memory512MB | egoman | bcm2835 | board1B | 2, nil // v2.0 + case 0x10: + return w | newFormat | memory512MB | sonyUK | bcm2835 | board1BPlus | 2, nil // v1.2 + case 0x11: + return w | newFormat | memory512MB | sonyUK | bcm2835 | boardCM1, nil + case 0x12: + return w | newFormat | memory256MB | sonyUK | bcm2835 | board1APlus | 1, nil // v1.1 + case 0x13: + return w | newFormat | memory512MB | embest | bcm2835 | board1BPlus | 2, nil // v1.2 + case 0x14: + return w | newFormat | memory512MB | embest | bcm2835 | boardCM1, nil + case 0x15: + // Can be either 256MB or 512MB. + return w | newFormat | memory256MB | embest | bcm2835 | board1APlus | 1, nil // v1.1 + default: + if v&uint32(newFormat) == 0 { + return 0, fmt.Errorf("rpi: unknown hardware version: 0x%x", v) + } + return revisionCode(v), nil + } +} + +const ( + warrantyVoid revisionCode = 1 << 24 + newFormat revisionCode = 1 << 23 + memoryShift = 20 + memoryMask revisionCode = 0x7 << memoryShift + manufacturerShift = 16 + manufacturerMask revisionCode = 0xf << manufacturerShift + processorShift = 12 + processorMask revisionCode = 0xf << processorShift + boardShift = 4 + boardMask revisionCode = 0xff << boardShift + revisionMask revisionCode = 0xf << 0 + + memory256MB revisionCode = 0 << memoryShift + memory512MB revisionCode = 1 << memoryShift + memory1GB revisionCode = 2 << memoryShift + memory2GB revisionCode = 3 << memoryShift + memory4GB revisionCode = 4 << memoryShift + + sonyUK revisionCode = 0 << manufacturerShift + egoman revisionCode = 1 << manufacturerShift + embest revisionCode = 2 << manufacturerShift + sonyJapan revisionCode = 3 << manufacturerShift + embest2 revisionCode = 4 << manufacturerShift + stadium revisionCode = 5 << manufacturerShift + + bcm2835 revisionCode = 0 << processorShift + bcm2836 revisionCode = 1 << processorShift + bcm2837 revisionCode = 2 << processorShift + bcm2711 revisionCode = 3 << processorShift + + board1A revisionCode = 0x0 << boardShift + board1B revisionCode = 0x1 << boardShift + board1APlus revisionCode = 0x2 << boardShift + board1BPlus revisionCode = 0x3 << boardShift + board2B revisionCode = 0x4 << boardShift + boardAlpha revisionCode = 0x5 << boardShift + boardCM1 revisionCode = 0x6 << boardShift + board3B revisionCode = 0x8 << boardShift + boardZero revisionCode = 0x9 << boardShift + boardCM3 revisionCode = 0xa << boardShift + boardZeroW revisionCode = 0xc << boardShift + board3BPlus revisionCode = 0xd << boardShift + board3APlus revisionCode = 0xe << boardShift + boardReserved revisionCode = 0xf << boardShift + boardCM3Plus revisionCode = 0x10 << boardShift + board4B revisionCode = 0x11 << boardShift +) + +// features represents the different features on various Raspberry Pi boards. +// +// See https://github.com/raspberrypi/firmware/blob/master/extra/dt-blob.dts +// for the official mapping. +type features struct { + hdrP1P26 bool // P1 has 26 pins + hdrP1P40 bool // P1 has 40 pins + hdrP5 bool // P5 is present + hdrAudio bool // Audio header is present + audioLeft41 bool // AUDIO_LEFT uses GPIO41 (RPi3 and later) instead of GPIO45 (old boards) + hdrHDMI bool // At least one HDMI port is present + hdrSODIMM bool // SODIMM port is present +} + +func (f *features) init(v uint32) error { + r, err := parseRevision(v) + if err != nil { + return err + } + // Ignore the overclock bit. + r &^= warrantyVoid + switch r & boardMask { + case board1A: + f.hdrP1P26 = true + f.hdrAudio = true + // Only the v2 PCB has the P5 header. + if r&revisionMask == 2 { + f.hdrP5 = true + f.hdrHDMI = true + } + case board1B: + f.hdrP1P26 = true + f.hdrAudio = true + // Only the v2 PCB has the P5 header. + if r&revisionMask == 2 { + f.hdrP5 = true + f.hdrHDMI = true + } + case board1APlus: + f.hdrP1P40 = true + f.hdrAudio = true + f.hdrHDMI = true + case board1BPlus: + f.hdrP1P40 = true + f.hdrAudio = true + f.hdrHDMI = true + case board2B: + f.hdrP1P40 = true + f.hdrAudio = true + f.hdrHDMI = true + case boardAlpha: + case boardCM1: + // TODO: define CM1 SODIMM header if anyone ever needs it. Please file an + // issue at https://github.com/google/periph/issues/new/choose + case board3B: + f.hdrP1P40 = true + f.hdrAudio = true + f.audioLeft41 = true + f.hdrHDMI = true + case boardZero: + f.hdrP1P40 = true + f.hdrHDMI = true + case boardCM3: + // Tell CM3 and CM3-Lite apart, if possible. + f.hdrSODIMM = true + case boardZeroW: + f.hdrP1P40 = true + f.hdrHDMI = true + case board3BPlus: + f.hdrP1P40 = true + f.hdrAudio = true + f.audioLeft41 = true + f.hdrHDMI = true + case board3APlus: + f.hdrP1P40 = true + f.hdrAudio = true + f.audioLeft41 = true + f.hdrHDMI = true + case boardReserved: + case boardCM3Plus: + // Tell CM3 and CM3-Lite apart, if possible. + f.hdrSODIMM = true + case board4B: + f.hdrP1P40 = true + f.hdrAudio = true + f.audioLeft41 = true + f.hdrHDMI = true + default: + return fmt.Errorf("rpi: unknown hardware version: 0x%x", r) + } + return nil +} + +// registerHeaders registers the headers for this board and fixes the GPIO +// global variables. +func (f *features) registerHeaders() error { + if f.hdrP1P26 { + if err := pinreg.Register("P1", [][]pin.Pin{ + {P1_1, P1_2}, + {P1_3, P1_4}, + {P1_5, P1_6}, + {P1_7, P1_8}, + {P1_9, P1_10}, + {P1_11, P1_12}, + {P1_13, P1_14}, + {P1_15, P1_16}, + {P1_17, P1_18}, + {P1_19, P1_20}, + {P1_21, P1_22}, + {P1_23, P1_24}, + {P1_25, P1_26}, + }); err != nil { + return err + } + + // TODO(maruel): Models from 2012 and earlier have P1_3=GPIO0, P1_5=GPIO1 and P1_13=GPIO21. + // P2 and P3 are not useful. + // P6 has a RUN pin for reset but it's not available after Pi version 1. + P1_27 = gpio.INVALID + P1_28 = gpio.INVALID + P1_29 = gpio.INVALID + P1_30 = pin.INVALID + P1_31 = gpio.INVALID + P1_32 = gpio.INVALID + P1_33 = gpio.INVALID + P1_34 = pin.INVALID + P1_35 = gpio.INVALID + P1_36 = gpio.INVALID + P1_37 = gpio.INVALID + P1_38 = gpio.INVALID + P1_39 = pin.INVALID + P1_40 = gpio.INVALID + } else if f.hdrP1P40 { + if err := pinreg.Register("P1", [][]pin.Pin{ + {P1_1, P1_2}, + {P1_3, P1_4}, + {P1_5, P1_6}, + {P1_7, P1_8}, + {P1_9, P1_10}, + {P1_11, P1_12}, + {P1_13, P1_14}, + {P1_15, P1_16}, + {P1_17, P1_18}, + {P1_19, P1_20}, + {P1_21, P1_22}, + {P1_23, P1_24}, + {P1_25, P1_26}, + {P1_27, P1_28}, + {P1_29, P1_30}, + {P1_31, P1_32}, + {P1_33, P1_34}, + {P1_35, P1_36}, + {P1_37, P1_38}, + {P1_39, P1_40}, + }); err != nil { + return err + } + } else { + P1_1 = pin.INVALID + P1_2 = pin.INVALID + P1_3 = gpio.INVALID + P1_4 = pin.INVALID + P1_5 = gpio.INVALID + P1_6 = pin.INVALID + P1_7 = gpio.INVALID + P1_8 = gpio.INVALID + P1_9 = pin.INVALID + P1_10 = gpio.INVALID + P1_11 = gpio.INVALID + P1_12 = gpio.INVALID + P1_13 = gpio.INVALID + P1_14 = pin.INVALID + P1_15 = gpio.INVALID + P1_16 = gpio.INVALID + P1_17 = pin.INVALID + P1_18 = gpio.INVALID + P1_19 = gpio.INVALID + P1_20 = pin.INVALID + P1_21 = gpio.INVALID + P1_22 = gpio.INVALID + P1_23 = gpio.INVALID + P1_24 = gpio.INVALID + P1_25 = pin.INVALID + P1_26 = gpio.INVALID + P1_27 = gpio.INVALID + P1_28 = gpio.INVALID + P1_29 = gpio.INVALID + P1_30 = pin.INVALID + P1_31 = gpio.INVALID + P1_32 = gpio.INVALID + P1_33 = gpio.INVALID + P1_34 = pin.INVALID + P1_35 = gpio.INVALID + P1_36 = gpio.INVALID + P1_37 = gpio.INVALID + P1_38 = gpio.INVALID + P1_39 = pin.INVALID + P1_40 = gpio.INVALID + } + + // Only the A and B v2 PCB has the P5 header. + if f.hdrP5 { + if err := pinreg.Register("P5", [][]pin.Pin{ + {P5_1, P5_2}, + {P5_3, P5_4}, + {P5_5, P5_6}, + {P5_7, P5_8}, + }); err != nil { + return err + } + } else { + P5_1 = pin.INVALID + P5_2 = pin.INVALID + P5_3 = gpio.INVALID + P5_4 = gpio.INVALID + P5_5 = gpio.INVALID + P5_6 = gpio.INVALID + P5_7 = pin.INVALID + P5_8 = pin.INVALID + } + + if f.hdrSODIMM { + if err := pinreg.Register("SO", [][]pin.Pin{ + {SO_1, SO_2}, + {SO_3, SO_4}, + {SO_5, SO_6}, + {SO_7, SO_8}, + {SO_9, SO_10}, + {SO_11, SO_12}, + {SO_13, SO_14}, + {SO_15, SO_16}, + {SO_17, SO_18}, + {SO_19, SO_20}, + {SO_21, SO_22}, + {SO_23, SO_24}, + {SO_25, SO_26}, + {SO_27, SO_28}, + {SO_29, SO_30}, + {SO_31, SO_32}, + {SO_33, SO_34}, + {SO_35, SO_36}, + {SO_37, SO_38}, + {SO_39, SO_40}, + {SO_41, SO_42}, + {SO_43, SO_44}, + {SO_45, SO_46}, + {SO_47, SO_48}, + {SO_49, SO_50}, + {SO_51, SO_52}, + {SO_53, SO_54}, + {SO_55, SO_56}, + {SO_57, SO_58}, + {SO_59, SO_60}, + {SO_61, SO_62}, + {SO_63, SO_64}, + {SO_65, SO_66}, + {SO_67, SO_68}, + {SO_69, SO_70}, + {SO_71, SO_72}, + {SO_73, SO_74}, + {SO_75, SO_76}, + {SO_77, SO_78}, + {SO_79, SO_80}, + {SO_81, SO_82}, + {SO_83, SO_84}, + {SO_85, SO_86}, + {SO_87, SO_88}, + {SO_89, SO_90}, + {SO_91, SO_92}, + {SO_93, SO_94}, + {SO_95, SO_96}, + {SO_97, SO_98}, + {SO_99, SO_100}, + {SO_101, SO_102}, + {SO_103, SO_104}, + {SO_105, SO_106}, + {SO_107, SO_108}, + {SO_109, SO_110}, + {SO_111, SO_112}, + {SO_113, SO_114}, + {SO_115, SO_116}, + {SO_117, SO_118}, + {SO_119, SO_120}, + {SO_121, SO_122}, + {SO_123, SO_124}, + {SO_125, SO_126}, + {SO_127, SO_128}, + {SO_129, SO_130}, + {SO_131, SO_132}, + {SO_133, SO_134}, + {SO_135, SO_136}, + {SO_137, SO_138}, + {SO_139, SO_140}, + {SO_141, SO_142}, + {SO_143, SO_144}, + {SO_145, SO_146}, + {SO_147, SO_148}, + {SO_149, SO_150}, + {SO_151, SO_152}, + {SO_153, SO_154}, + {SO_155, SO_156}, + {SO_157, SO_158}, + {SO_159, SO_160}, + {SO_161, SO_162}, + {SO_163, SO_164}, + {SO_165, SO_166}, + {SO_167, SO_168}, + {SO_169, SO_170}, + {SO_171, SO_172}, + {SO_173, SO_174}, + {SO_175, SO_176}, + {SO_177, SO_178}, + {SO_179, SO_180}, + {SO_181, SO_182}, + {SO_183, SO_184}, + {SO_185, SO_186}, + {SO_187, SO_188}, + {SO_189, SO_190}, + {SO_191, SO_192}, + {SO_193, SO_194}, + {SO_195, SO_196}, + {SO_197, SO_198}, + {SO_199, SO_200}, + }); err != nil { + return err + } + } + + if f.hdrAudio { + // Two early versions of RPi1 had left and right reversed but we don't + // bother handling this here. + // https://github.com/raspberrypi/firmware/blob/master/extra/dt-blob.dts + if !f.audioLeft41 { + AUDIO_LEFT = bcm283x.GPIO45 // PWM1 for older boards + } + if err := pinreg.Register("AUDIO", [][]pin.Pin{ + {AUDIO_LEFT}, + {AUDIO_RIGHT}, + }); err != nil { + return err + } + } + + if f.hdrHDMI { + if err := pinreg.Register("HDMI", [][]pin.Pin{{HDMI_HOTPLUG_DETECT}}); err != nil { + return err + } + } + return nil +} + +// driver implements periph.Driver. +type driver struct { +} + +func (d *driver) String() string { + return "rpi" +} + +func (d *driver) Prerequisites() []string { + return nil +} + +func (d *driver) After() []string { + return []string{"bcm283x-gpio"} +} + +func (d *driver) Init() (bool, error) { + if !Present() { + return false, errors.New("Raspberry Pi board not detected") + } + + // Setup headers based on board revision. + // + // This code is not futureproof, it will error out on a Raspberry Pi 4 + // whenever it comes out. + // Revision codes from: http://elinux.org/RPi_HardwareHistory + f := features{} + rev := distro.CPUInfo()["Revision"] + if v, err := strconv.ParseUint(rev, 16, 32); err == nil { + if err := f.init(uint32(v)); err != nil { + return true, err + } + } else { + return true, fmt.Errorf("rpi: failed to read cpu_info: %v", err) + } + + return true, f.registerHeaders() +} + +func init() { + if isArm { + periph.MustRegister(&drv) + } +} + +var drv driver diff --git a/vendor/periph.io/x/periph/host/rpi/rpi_arm.go b/vendor/periph.io/x/periph/host/rpi/rpi_arm.go new file mode 100644 index 0000000..acf1d96 --- /dev/null +++ b/vendor/periph.io/x/periph/host/rpi/rpi_arm.go @@ -0,0 +1,7 @@ +// Copyright 2016 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 rpi + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/rpi/rpi_arm64.go b/vendor/periph.io/x/periph/host/rpi/rpi_arm64.go new file mode 100644 index 0000000..ad1347a --- /dev/null +++ b/vendor/periph.io/x/periph/host/rpi/rpi_arm64.go @@ -0,0 +1,9 @@ +// Copyright 2016 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. + +// +build arm64 + +package rpi + +const isArm = true diff --git a/vendor/periph.io/x/periph/host/rpi/rpi_other.go b/vendor/periph.io/x/periph/host/rpi/rpi_other.go new file mode 100644 index 0000000..414d714 --- /dev/null +++ b/vendor/periph.io/x/periph/host/rpi/rpi_other.go @@ -0,0 +1,9 @@ +// Copyright 2016 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. + +// +build !arm,!arm64 + +package rpi + +const isArm = false diff --git a/vendor/periph.io/x/periph/host/sysfs/doc.go b/vendor/periph.io/x/periph/host/sysfs/doc.go new file mode 100644 index 0000000..87eb973 --- /dev/null +++ b/vendor/periph.io/x/periph/host/sysfs/doc.go @@ -0,0 +1,13 @@ +// Copyright 2016 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 sysfs implements a sane library to interact with sysfs provided +// hardware access. +// +// sysfs a virtual file system rooted at /sys/. +// +// This package also include drivers using devfs. +// +// https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt +package sysfs diff --git a/vendor/periph.io/x/periph/host/sysfs/fs_linux.go b/vendor/periph.io/x/periph/host/sysfs/fs_linux.go new file mode 100644 index 0000000..4201109 --- /dev/null +++ b/vendor/periph.io/x/periph/host/sysfs/fs_linux.go @@ -0,0 +1,317 @@ +// 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 sysfs + +import ( + "errors" + "os" + "strconv" + "strings" + "sync" + "sync/atomic" + "syscall" + "time" +) + +// syscall.EpollCtl() commands. +// +// These are defined here so we don't need to import golang.org/x/sys/unix. +// +// http://man7.org/linux/man-pages/man2/epoll_ctl.2.html +const ( + epollCTLAdd = 1 // EPOLL_CTL_ADD + epollCTLDel = 2 // EPOLL_CTL_DEL + epollCTLMod = 3 // EPOLL_CTL_MOD +) + +// Bitmask for field syscall.EpollEvent.Events. +// +// These are defined here so we don't need to import golang.org/x/sys/unix. +// +// http://man7.org/linux/man-pages/man2/epoll_ctl.2.html +type epollEvent uint32 + +const ( + epollIN epollEvent = 0x1 // EPOLLIN: available for read + epollOUT epollEvent = 0x4 // EPOLLOUT: available for write + epollPRI epollEvent = 0x2 // EPOLLPRI: exceptional urgent condition + epollERR epollEvent = 0x8 // EPOLLERR: error + epollHUP epollEvent = 0x10 // EPOLLHUP: hangup + epollET epollEvent = 0x80000000 // EPOLLET: Edge Triggered behavior + epollONESHOT epollEvent = 0x40000000 // EPOLLONESHOT: One shot + epollWAKEUP epollEvent = 0x20000000 // EPOLLWAKEUP: disable system sleep; kernel >=3.5 + epollEXCLUSIVE epollEvent = 0x10000000 // EPOLLEXCLUSIVE: only wake one; kernel >=4.5 +) + +var bitmaskString = [...]struct { + e epollEvent + s string +}{ + {epollIN, "IN"}, + {epollOUT, "OUT"}, + {epollPRI, "PRI"}, + {epollERR, "ERR"}, + {epollHUP, "HUP"}, + {epollET, "ET"}, + {epollONESHOT, "ONESHOT"}, + {epollWAKEUP, "WAKEUP"}, + {epollEXCLUSIVE, "EXCLUSIVE"}, +} + +// String is useful for debugging. +func (e epollEvent) String() string { + var out []string + for _, b := range bitmaskString { + if e&b.e != 0 { + out = append(out, b.s) + e &^= b.e + } + } + if e != 0 { + out = append(out, "0x"+strconv.FormatUint(uint64(e), 16)) + } + if len(out) == 0 { + out = []string{"0"} + } + return strings.Join(out, "|") +} + +// eventsListener listens for events for multiple files as a single system call. +// +// One OS thread is needed for all the events. This is more efficient on single +// core system. +type eventsListener struct { + // Atomic value set to one once fully initialized. + initialized int32 + + // Mapping of file descriptors to wait on with their corresponding channels. + mu sync.Mutex + // File descriptor of the epoll handle itself. + epollFd int + // Pipes to wake up the EpollWait() system call inside loop(). + r, w *os.File + // Return channel to confirm that EpollWait() was woken up. + wakeUp <-chan time.Time + // Map of file handles to user listening channel. + fds map[int32]chan<- time.Time +} + +// init must be called on a fresh instance. +func (e *eventsListener) init() error { + if atomic.LoadInt32(&e.initialized) != 0 { + // Was already initialized. + return nil + } + e.mu.Lock() + if atomic.LoadInt32(&e.initialized) != 0 { + // Was already initialized, but this was done concurrently with another + // thread. + e.mu.Unlock() + return nil + } + var err error + e.epollFd, err = syscall.EpollCreate(1) + switch { + case err == nil: + break + case err.Error() == "function not implemented": + // Some arch (arm64) do not implement EpollCreate(). + if e.epollFd, err = syscall.EpollCreate1(0); err != nil { + e.mu.Unlock() + return err + } + default: + e.mu.Unlock() + return err + } + e.r, e.w, err = os.Pipe() + // Only need epollIN. epollPRI has no effect on pipes. + if err := e.addFdInner(e.r.Fd(), epollET|epollIN); err != nil { + // This object will not be reusable at this point. + e.mu.Unlock() + return err + } + wakeUp := make(chan time.Time) + e.wakeUp = wakeUp + e.fds = map[int32]chan<- time.Time{} + e.fds[int32(e.r.Fd())] = wakeUp + // The mutex is still held after this function exits, it's loop() that will + // release the mutex. + // + // This forces loop() to be started before addFd() can be called by users. + go e.loop() + // Initialization is now good to go. + atomic.StoreInt32(&e.initialized, 1) + return nil +} + +// loop is the main event loop. +func (e *eventsListener) loop() { + var events []syscall.EpollEvent + type lookup struct { + c chan<- time.Time + event epollEvent + } + var lookups []lookup + for first := true; ; { + if !first { + e.mu.Lock() + } + if len(events) < len(e.fds) { + events = make([]syscall.EpollEvent, len(e.fds)) + } + e.mu.Unlock() + first = false + + if len(events) == 0 { + panic("internal error: there's should be at least one pipe") + } + + // http://man7.org/linux/man-pages/man2/epoll_wait.2.html + n, err := syscall.EpollWait(e.epollFd, events, -1) + if n <= 0 { + // -1 if an error occurred (EBADF, EFAULT, EINVAL) or the call was + // interrupted by a signal (EINTR). + // 0 is the timeout occurred. In this case there's no timeout specified. + // Still handle this explicitly in case a timeout could be triggered by + // external events, like system sleep. + continue + } + if err != nil { + // TODO(maruel): It'd be nice to be able to surface this. + // This may cause a busy loop. Hopefully the user will notice and will + // fix their code. + // This can happen when removeFd() is called, in this case silently + // ignore the error. + continue + } + + now := time.Now() + // Create a look up table with the lock, so that then the channel can be + // pushed to without the lock. + if cap(lookups) < n { + lookups = make([]lookup, 0, n) + } else { + lookups = lookups[:0] + } + + e.mu.Lock() + for _, ev := range events[:n] { + ep := epollEvent(ev.Events) + // Skip over file descriptors that are not present. + c, ok := e.fds[ev.Fd] + if !ok { + // That's a race condition where the file descriptor was removed by + // removeFd() but it still triggered. Ignore this event. + continue + } + // Look at the event to determine if it's worth sending a pulse. It's + // maybe not worth it. Ignore epollERR, since it's always set for GPIO + // sysfs. + // Pipe and socket trigger epollIN and epollOUT, but GPIO sysfs triggers + // epollPRI. + if ep&(epollPRI|epollIN|epollOUT) != 0 { + lookups = append(lookups, lookup{c: c, event: ep}) + } + } + e.mu.Unlock() + + // Once the lock is released, send the timestamps. + for _, t := range lookups { + t.c <- now + } + } +} + +// addFd starts listening to events generated by file descriptor |fd|. +// +// fd is the OS file descriptor. In practice, it must fit a int32 value. It +// works on pipes, sockets and sysfs objects like GPIO but not on standard +// files. +// +// c is the channel to send events to. Unbuffered channel will block the event +// loop, which may mean lost events, especially if multiple files are listened +// to simultaneously. +// +// flags is the events to listen to. No need to specify epollERR and epollHUP, +// they are sent anyway. +// +// addFd lazy initializes eventsListener if it was not initialized yet. +// +// It can fail due to various reasons, a few are: +// ENOSPC: /proc/sys/fs/epoll/max_user_watches limit was exceeded +// ENOMEM: No memory available +// EPERM: fd is a regular file or directory +func (e *eventsListener) addFd(fd uintptr, c chan<- time.Time, flags epollEvent) error { + if c == nil { + return errors.New("fd: addFd requires a valid channel") + } + if err := e.init(); err != nil { + return err + } + if err := e.addFdInner(fd, flags); err != nil { + return err + } + e.mu.Lock() + e.fds[int32(fd)] = c + e.mu.Unlock() + // Wake up the poller so it notices there's one new file. + e.wakeUpLoop(nil) + return nil +} + +func (e *eventsListener) addFdInner(fd uintptr, flags epollEvent) error { + ev := syscall.EpollEvent{Events: uint32(flags), Fd: int32(fd)} + return syscall.EpollCtl(e.epollFd, epollCTLAdd, int(fd), &ev) +} + +// removeFd stop listening to events on this file descriptor. +func (e *eventsListener) removeFd(fd uintptr) error { + if err := syscall.EpollCtl(e.epollFd, epollCTLDel, int(fd), nil); err != nil { + return err + } + e.mu.Lock() + delete(e.fds, int32(fd)) + e.mu.Unlock() + // Wake up the poller so it notices there's one less file. + e.wakeUpLoop(nil) + return nil +} + +// wakeUpLoop wakes up the poller and waits for it. +// +// Must not be called with the lock held. +func (e *eventsListener) wakeUpLoop(c <-chan time.Time) time.Time { + if atomic.LoadInt32(&e.initialized) == 0 { + return time.Time{} + } + // TODO(maruel): Figure out a way to wake up that doesn't require emptying. + var b [1]byte + _, _ = e.w.Write(b[:]) + var t time.Time + if c != nil { + // To prevent deadlock, also empty c. + for { + select { + case <-c: + case t = <-e.wakeUp: + goto out + } + } + out: + } else { + t = <-e.wakeUp + } + // Don't forget to empty the pipe. Sadly, this will wake up the loop a second + // time. + _, _ = e.r.Read(b[:]) + return t +} + +// events is the global events listener. +// +// It uses a single global goroutine lazily initialized to call +// syscall.EpollWait() to listen to many file descriptors at once. +var events eventsListener diff --git a/vendor/periph.io/x/periph/host/sysfs/fs_other.go b/vendor/periph.io/x/periph/host/sysfs/fs_other.go new file mode 100644 index 0000000..62678cc --- /dev/null +++ b/vendor/periph.io/x/periph/host/sysfs/fs_other.go @@ -0,0 +1,15 @@ +// 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. + +// +build !linux + +package sysfs + +type eventsListener struct { +} + +// events is the global events listener. +// +// It is not used outside linux. +var events eventsListener diff --git a/vendor/periph.io/x/periph/host/sysfs/gpio.go b/vendor/periph.io/x/periph/host/sysfs/gpio.go new file mode 100644 index 0000000..c751bad --- /dev/null +++ b/vendor/periph.io/x/periph/host/sysfs/gpio.go @@ -0,0 +1,520 @@ +// Copyright 2016 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 sysfs + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strconv" + "sync" + "time" + + "periph.io/x/periph" + "periph.io/x/periph/conn" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/gpio/gpioreg" + "periph.io/x/periph/conn/physic" + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/host/fs" +) + +// Pins is all the pins exported by GPIO sysfs. +// +// Some CPU architectures have the pin numbers start at 0 and use consecutive +// pin numbers but this is not the case for all CPU architectures, some +// have gaps in the pin numbering. +// +// This global variable is initialized once at driver initialization and isn't +// mutated afterward. Do not modify it. +var Pins map[int]*Pin + +// Pin represents one GPIO pin as found by sysfs. +type Pin struct { + number int + name string + root string // Something like /sys/class/gpio/gpio%d/ + + mu sync.Mutex + err error // If open() failed + direction direction // Cache of the last known direction + edge gpio.Edge // Cache of the last edge used. + fDirection fileIO // handle to /sys/class/gpio/gpio*/direction; never closed + fEdge fileIO // handle to /sys/class/gpio/gpio*/edge; never closed + fValue fileIO // handle to /sys/class/gpio/gpio*/value; never closed + event fs.Event // Initialized once + buf [4]byte // scratch buffer for Func(), Read() and Out() +} + +// String implements conn.Resource. +func (p *Pin) String() string { + return p.name +} + +// Halt implements conn.Resource. +// +// It stops edge detection if enabled. +func (p *Pin) Halt() error { + p.mu.Lock() + defer p.mu.Unlock() + return p.haltEdge() +} + +// Name implements pin.Pin. +func (p *Pin) Name() string { + return p.name +} + +// Number implements pin.Pin. +func (p *Pin) Number() int { + return p.number +} + +// Function implements pin.Pin. +func (p *Pin) Function() string { + return string(p.Func()) +} + +// Func implements pin.PinFunc. +func (p *Pin) Func() pin.Func { + p.mu.Lock() + defer p.mu.Unlock() + // TODO(maruel): There's an internal bug which causes p.direction to be + // invalid (!?) Need to figure it out ASAP. + if err := p.open(); err != nil { + return pin.FuncNone + } + if _, err := seekRead(p.fDirection, p.buf[:]); err != nil { + return pin.FuncNone + } + if p.buf[0] == 'i' && p.buf[1] == 'n' { + p.direction = dIn + } else if p.buf[0] == 'o' && p.buf[1] == 'u' && p.buf[2] == 't' { + p.direction = dOut + } + if p.direction == dIn { + if p.Read() { + return gpio.IN_HIGH + } + return gpio.IN_LOW + } else if p.direction == dOut { + if p.Read() { + return gpio.OUT_HIGH + } + return gpio.OUT_LOW + } + return pin.FuncNone +} + +// SupportedFuncs implements pin.PinFunc. +func (p *Pin) SupportedFuncs() []pin.Func { + return []pin.Func{gpio.IN, gpio.OUT} +} + +// SetFunc implements pin.PinFunc. +func (p *Pin) SetFunc(f pin.Func) error { + switch f { + case gpio.IN: + return p.In(gpio.PullNoChange, gpio.NoEdge) + case gpio.OUT_HIGH: + return p.Out(gpio.High) + case gpio.OUT, gpio.OUT_LOW: + return p.Out(gpio.Low) + default: + return p.wrap(errors.New("unsupported function")) + } +} + +// In implements gpio.PinIn. +func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error { + if pull != gpio.PullNoChange && pull != gpio.Float { + return p.wrap(errors.New("doesn't support pull-up/pull-down")) + } + p.mu.Lock() + defer p.mu.Unlock() + if p.direction != dIn { + if err := p.open(); err != nil { + return p.wrap(err) + } + if err := seekWrite(p.fDirection, bIn); err != nil { + return p.wrap(err) + } + p.direction = dIn + } + // Always push none to help accumulated flush edges. This is not fool proof + // but it seems to help. + if p.fEdge != nil { + if err := seekWrite(p.fEdge, bNone); err != nil { + return p.wrap(err) + } + } + // Assume that when the pin was switched, the driver doesn't recall if edge + // triggering was enabled. + if edge != gpio.NoEdge { + if p.fEdge == nil { + var err error + p.fEdge, err = fileIOOpen(p.root+"edge", os.O_RDWR) + if err != nil { + return p.wrap(err) + } + if err = p.event.MakeEvent(p.fValue.Fd()); err != nil { + _ = p.fEdge.Close() + p.fEdge = nil + return p.wrap(err) + } + } + // Always reset the edge detection mode to none after starting the epoll + // otherwise edges are not always delivered, as observed on an Allwinner A20 + // running kernel 4.14.14. + if err := seekWrite(p.fEdge, bNone); err != nil { + return p.wrap(err) + } + var b []byte + switch edge { + case gpio.RisingEdge: + b = bRising + case gpio.FallingEdge: + b = bFalling + case gpio.BothEdges: + b = bBoth + } + if err := seekWrite(p.fEdge, b); err != nil { + return p.wrap(err) + } + } + p.edge = edge + // This helps to remove accumulated edges but this is not 100% sufficient. + // Most of the time the interrupts are handled promptly enough that this loop + // flushes the accumulated interrupt. + // Sometimes the kernel may have accumulated interrupts that haven't been + // processed for a long time, it can easily be >300µs even on a quite idle + // CPU. In this case, the loop below is not sufficient, since the interrupt + // will happen afterward "out of the blue". + if edge != gpio.NoEdge { + p.WaitForEdge(0) + } + return nil +} + +// Read implements gpio.PinIn. +func (p *Pin) Read() gpio.Level { + // There's no lock here. + if p.fValue == nil { + return gpio.Low + } + if _, err := seekRead(p.fValue, p.buf[:]); err != nil { + // Error. + return gpio.Low + } + if p.buf[0] == '0' { + return gpio.Low + } + if p.buf[0] == '1' { + return gpio.High + } + // Error. + return gpio.Low +} + +// WaitForEdge implements gpio.PinIn. +func (p *Pin) WaitForEdge(timeout time.Duration) bool { + // Run lockless, as the normal use is to call in a busy loop. + var ms int + if timeout == -1 { + ms = -1 + } else { + ms = int(timeout / time.Millisecond) + } + start := time.Now() + for { + if nr, err := p.event.Wait(ms); err != nil { + return false + } else if nr == 1 { + // TODO(maruel): According to pigpio, the correct way to consume the + // interrupt is to call Seek(). + return true + } + // A signal occurred. + if timeout != -1 { + ms = int((timeout - time.Since(start)) / time.Millisecond) + } + if ms <= 0 { + return false + } + } +} + +// Pull implements gpio.PinIn. +// +// It returns gpio.PullNoChange since gpio sysfs has no support for input pull +// resistor. +func (p *Pin) Pull() gpio.Pull { + return gpio.PullNoChange +} + +// DefaultPull implements gpio.PinIn. +// +// It returns gpio.PullNoChange since gpio sysfs has no support for input pull +// resistor. +func (p *Pin) DefaultPull() gpio.Pull { + return gpio.PullNoChange +} + +// Out implements gpio.PinOut. +func (p *Pin) Out(l gpio.Level) error { + p.mu.Lock() + defer p.mu.Unlock() + if p.direction != dOut { + if err := p.open(); err != nil { + return p.wrap(err) + } + if err := p.haltEdge(); err != nil { + return err + } + // "To ensure glitch free operation, values "low" and "high" may be written + // to configure the GPIO as an output with that initial value." + var d []byte + if l == gpio.Low { + d = bLow + } else { + d = bHigh + } + if err := seekWrite(p.fDirection, d); err != nil { + return p.wrap(err) + } + p.direction = dOut + return nil + } + if l == gpio.Low { + p.buf[0] = '0' + } else { + p.buf[0] = '1' + } + if err := seekWrite(p.fValue, p.buf[:1]); err != nil { + return p.wrap(err) + } + return nil +} + +// PWM implements gpio.PinOut. +// +// This is not supported on sysfs. +func (p *Pin) PWM(gpio.Duty, physic.Frequency) error { + return p.wrap(errors.New("pwm is not supported via sysfs")) +} + +// + +// open opens the gpio sysfs handle to /value and /direction. +// +// lock must be held. +func (p *Pin) open() error { + if p.fDirection != nil || p.err != nil { + return p.err + } + + if drvGPIO.exportHandle == nil { + return errors.New("sysfs gpio is not initialized") + } + + // Try to open the pin if it was there. It's possible it had been exported + // already. + if p.fValue, p.err = fileIOOpen(p.root+"value", os.O_RDWR); p.err == nil { + // Fast track. + goto direction + } else if !os.IsNotExist(p.err) { + // It exists but not accessible, not worth doing the remainder. + p.err = fmt.Errorf("need more access, try as root or setup udev rules: %v", p.err) + return p.err + } + + if _, p.err = drvGPIO.exportHandle.Write([]byte(strconv.Itoa(p.number))); p.err != nil && !isErrBusy(p.err) { + if os.IsPermission(p.err) { + p.err = fmt.Errorf("need more access, try as root or setup udev rules: %v", p.err) + } + return p.err + } + + // There's a race condition where the file may be created but udev is still + // running the Raspbian udev rule to make it readable to the current user. + // It's simpler to just loop a little as if /export is accessible, it doesn't + // make sense that gpioN/value doesn't become accessible eventually. + for start := time.Now(); time.Since(start) < 5*time.Second; { + // The virtual file creation is synchronous when writing to /export; albeit + // udev rule execution is asynchronous, so file mode change via udev rules + // takes some time to propagate. + if p.fValue, p.err = fileIOOpen(p.root+"value", os.O_RDWR); p.err == nil || !os.IsPermission(p.err) { + // Either success or a failure that is not a permission error. + break + } + } + if p.err != nil { + return p.err + } + +direction: + if p.fDirection, p.err = fileIOOpen(p.root+"direction", os.O_RDWR); p.err != nil { + _ = p.fValue.Close() + p.fValue = nil + } + return p.err +} + +// haltEdge stops any on-going edge detection. +func (p *Pin) haltEdge() error { + if p.edge != gpio.NoEdge { + if err := seekWrite(p.fEdge, bNone); err != nil { + return p.wrap(err) + } + p.edge = gpio.NoEdge + // This is still important to remove an accumulated edge. + p.WaitForEdge(0) + } + return nil +} + +func (p *Pin) wrap(err error) error { + return fmt.Errorf("sysfs-gpio (%s): %v", p, err) +} + +// + +type direction int + +const ( + dUnknown direction = 0 + dIn direction = 1 + dOut direction = 2 +) + +var ( + bIn = []byte("in") + bLow = []byte("low") + bHigh = []byte("high") + bNone = []byte("none") + bRising = []byte("rising") + bFalling = []byte("falling") + bBoth = []byte("both") +) + +// readInt reads a pseudo-file (sysfs) that is known to contain an integer and +// returns the parsed number. +func readInt(path string) (int, error) { + f, err := fileIOOpen(path, os.O_RDONLY) + if err != nil { + return 0, err + } + defer f.Close() + var b [24]byte + n, err := f.Read(b[:]) + if err != nil { + return 0, err + } + raw := b[:n] + if len(raw) == 0 || raw[len(raw)-1] != '\n' { + return 0, errors.New("invalid value") + } + return strconv.Atoi(string(raw[:len(raw)-1])) +} + +// driverGPIO implements periph.Driver. +type driverGPIO struct { + exportHandle io.Writer // handle to /sys/class/gpio/export +} + +func (d *driverGPIO) String() string { + return "sysfs-gpio" +} + +func (d *driverGPIO) Prerequisites() []string { + return nil +} + +func (d *driverGPIO) After() []string { + return nil +} + +// Init initializes GPIO sysfs handling code. +// +// Uses gpio sysfs as described at +// https://www.kernel.org/doc/Documentation/gpio/sysfs.txt +// +// GPIO sysfs is often the only way to do edge triggered interrupts. Doing this +// requires cooperation from a driver in the kernel. +// +// The main drawback of GPIO sysfs is that it doesn't expose internal pull +// resistor and it is much slower than using memory mapped hardware registers. +func (d *driverGPIO) Init() (bool, error) { + items, err := filepath.Glob("/sys/class/gpio/gpiochip*") + if err != nil { + return true, err + } + if len(items) == 0 { + return false, errors.New("no GPIO pin found") + } + + // There are hosts that use non-continuous pin numbering so use a map instead + // of an array. + Pins = map[int]*Pin{} + for _, item := range items { + if err := d.parseGPIOChip(item + "/"); err != nil { + return true, err + } + } + drvGPIO.exportHandle, err = fileIOOpen("/sys/class/gpio/export", os.O_WRONLY) + if os.IsPermission(err) { + return true, fmt.Errorf("need more access, try as root or setup udev rules: %v", err) + } + return true, err +} + +func (d *driverGPIO) parseGPIOChip(path string) error { + base, err := readInt(path + "base") + if err != nil { + return err + } + number, err := readInt(path + "ngpio") + if err != nil { + return err + } + // TODO(maruel): The chip driver may lie and lists GPIO pins that cannot be + // exported. The only way to know about it is to export it before opening. + for i := base; i < base+number; i++ { + if _, ok := Pins[i]; ok { + return fmt.Errorf("found two pins with number %d", i) + } + p := &Pin{ + number: i, + name: fmt.Sprintf("GPIO%d", i), + root: fmt.Sprintf("/sys/class/gpio/gpio%d/", i), + } + Pins[i] = p + if err := gpioreg.Register(p); err != nil { + return err + } + // If there is a CPU memory mapped gpio pin with the same number, the + // driver has to unregister this pin and map its own after. + if err := gpioreg.RegisterAlias(strconv.Itoa(i), p.name); err != nil { + return err + } + } + return nil +} + +func init() { + if isLinux { + periph.MustRegister(&drvGPIO) + } +} + +var drvGPIO driverGPIO + +var _ conn.Resource = &Pin{} +var _ gpio.PinIn = &Pin{} +var _ gpio.PinOut = &Pin{} +var _ gpio.PinIO = &Pin{} +var _ pin.PinFunc = &Pin{} diff --git a/vendor/periph.io/x/periph/host/sysfs/i2c.go b/vendor/periph.io/x/periph/host/sysfs/i2c.go new file mode 100644 index 0000000..2112793 --- /dev/null +++ b/vendor/periph.io/x/periph/host/sysfs/i2c.go @@ -0,0 +1,388 @@ +// Copyright 2016 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 sysfs + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "sync" + "unsafe" + + "periph.io/x/periph" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/gpio/gpioreg" + "periph.io/x/periph/conn/i2c" + "periph.io/x/periph/conn/i2c/i2creg" + "periph.io/x/periph/conn/physic" +) + +// I2CSetSpeedHook can be set by a driver to enable changing the I²C buses +// speed. +func I2CSetSpeedHook(h func(f physic.Frequency) error) error { + if h == nil { + return errors.New("sysfs-i2c: hook must not be nil") + } + drvI2C.mu.Lock() + defer drvI2C.mu.Unlock() + if drvI2C.setSpeed != nil { + return errors.New("sysfs-i2c: a speed hook was already set") + } + drvI2C.setSpeed = h + return nil +} + +// NewI2C opens an I²C bus via its sysfs interface as described at +// https://www.kernel.org/doc/Documentation/i2c/dev-interface. +// +// busNumber is the bus number as exported by sysfs. For example if the path is +// /dev/i2c-1, busNumber should be 1. +// +// The resulting object is safe for concurent use. +// +// Do not use sysfs.NewI2C() directly as the package sysfs is providing a +// https://periph.io/x/periph/conn/i2c Linux-specific implementation. +// +// periph.io works on many OSes! +// +// Instead, use https://periph.io/x/periph/conn/i2c/i2creg#Open. This permits +// it to work on all operating systems, or devices like I²C over USB. +func NewI2C(busNumber int) (*I2C, error) { + if isLinux { + return newI2C(busNumber) + } + return nil, errors.New("sysfs-i2c: is not supported on this platform") +} + +// I2C is an open I²C bus via sysfs. +// +// It can be used to communicate with multiple devices from multiple goroutines. +type I2C struct { + f ioctlCloser + busNumber int + + mu sync.Mutex // In theory the kernel probably has an internal lock but not taking any chance. + fn functionality + scl gpio.PinIO + sda gpio.PinIO +} + +// Close closes the handle to the I²C driver. It is not a requirement to close +// before process termination. +func (i *I2C) Close() error { + i.mu.Lock() + defer i.mu.Unlock() + if err := i.f.Close(); err != nil { + return fmt.Errorf("sysfs-i2c: %v", err) + } + return nil +} + +func (i *I2C) String() string { + return fmt.Sprintf("I2C%d", i.busNumber) +} + +// Tx execute a transaction as a single operation unit. +func (i *I2C) Tx(addr uint16, w, r []byte) error { + if addr >= 0x400 || (addr >= 0x80 && i.fn&func10BitAddr == 0) { + return errors.New("sysfs-i2c: invalid address") + } + if len(w) == 0 && len(r) == 0 { + return nil + } + + // Convert the messages to the internal format. + var buf [2]i2cMsg + msgs := buf[0:0] + if len(w) != 0 { + msgs = buf[:1] + buf[0].addr = addr + buf[0].length = uint16(len(w)) + buf[0].buf = uintptr(unsafe.Pointer(&w[0])) + } + if len(r) != 0 { + l := len(msgs) + msgs = msgs[:l+1] // extend the slice by one + buf[l].addr = addr + buf[l].flags = flagRD + buf[l].length = uint16(len(r)) + buf[l].buf = uintptr(unsafe.Pointer(&r[0])) + } + p := rdwrIoctlData{ + msgs: uintptr(unsafe.Pointer(&msgs[0])), + nmsgs: uint32(len(msgs)), + } + pp := uintptr(unsafe.Pointer(&p)) + i.mu.Lock() + defer i.mu.Unlock() + if err := i.f.Ioctl(ioctlRdwr, pp); err != nil { + return fmt.Errorf("sysfs-i2c: %v", err) + } + return nil +} + +// SetSpeed implements i2c.Bus. +func (i *I2C) SetSpeed(f physic.Frequency) error { + if f > 100*physic.MegaHertz { + return fmt.Errorf("sysfs-i2c: invalid speed %s; maximum supported clock is 100MHz", f) + } + if f < physic.KiloHertz { + return fmt.Errorf("sysfs-i2c: invalid speed %s; minimum supported clock is 1KHz; did you forget to multiply by physic.KiloHertz?", f) + } + drvI2C.mu.Lock() + defer drvI2C.mu.Unlock() + if drvI2C.setSpeed != nil { + return drvI2C.setSpeed(f) + } + return errors.New("sysfs-i2c: not supported") +} + +// SCL implements i2c.Pins. +func (i *I2C) SCL() gpio.PinIO { + i.initPins() + return i.scl +} + +// SDA implements i2c.Pins. +func (i *I2C) SDA() gpio.PinIO { + i.initPins() + return i.sda +} + +// Private details. + +func newI2C(busNumber int) (*I2C, error) { + // Use the devfs path for now instead of sysfs path. + f, err := ioctlOpen(fmt.Sprintf("/dev/i2c-%d", busNumber), os.O_RDWR) + if err != nil { + // Try to be helpful here. There are generally two cases: + // - /dev/i2c-X doesn't exist. In this case, /boot/config.txt has to be + // edited to enable I²C then the device must be rebooted. + // - permission denied. In this case, the user has to be added to plugdev. + if os.IsNotExist(err) { + return nil, fmt.Errorf("sysfs-i2c: bus #%d is not configured: %v", busNumber, err) + } + // TODO(maruel): This is a debianism. + return nil, fmt.Errorf("sysfs-i2c: are you member of group 'plugdev'? %v", err) + } + i := &I2C{f: f, busNumber: busNumber} + + // TODO(maruel): Changing the speed is currently doing this for all devices. + // https://github.com/raspberrypi/linux/issues/215 + // Need to access /sys/module/i2c_bcm2708/parameters/baudrate + + // Query to know if 10 bits addresses are supported. + if err = i.f.Ioctl(ioctlFuncs, uintptr(unsafe.Pointer(&i.fn))); err != nil { + return nil, fmt.Errorf("sysfs-i2c: %v", err) + } + return i, nil +} + +func (i *I2C) initPins() { + i.mu.Lock() + if i.scl == nil { + if i.scl = gpioreg.ByName(fmt.Sprintf("I2C%d_SCL", i.busNumber)); i.scl == nil { + i.scl = gpio.INVALID + } + if i.sda = gpioreg.ByName(fmt.Sprintf("I2C%d_SDA", i.busNumber)); i.sda == nil { + i.sda = gpio.INVALID + } + } + i.mu.Unlock() +} + +// i2cdev driver IOCTL control codes. +// +// Constants and structure definition can be found at +// /usr/include/linux/i2c-dev.h and /usr/include/linux/i2c.h. +const ( + ioctlRetries = 0x701 // TODO(maruel): Expose this + ioctlTimeout = 0x702 // TODO(maruel): Expose this; in units of 10ms + ioctlSlave = 0x703 + ioctlTenBits = 0x704 // TODO(maruel): Expose this but the header says it's broken (!?) + ioctlFuncs = 0x705 + ioctlRdwr = 0x707 +) + +// flags +const ( + flagTEN = 0x0010 // this is a ten bit chip address + flagRD = 0x0001 // read data, from slave to master + flagSTOP = 0x8000 // if funcProtocolMangling + flagNOSTART = 0x4000 // if I2C_FUNC_NOSTART + flagRevDirAddr = 0x2000 // if funcProtocolMangling + flagIgnoreNAK = 0x1000 // if funcProtocolMangling + flagNoRDACK = 0x0800 // if funcProtocolMangling + flagRecvLen = 0x0400 // length will be first received byte + +) + +type functionality uint64 + +const ( + funcI2C = 0x00000001 + func10BitAddr = 0x00000002 + funcProtocolMangling = 0x00000004 // I2C_M_IGNORE_NAK etc. + funcSMBusPEC = 0x00000008 + funcNOSTART = 0x00000010 // I2C_M_NOSTART + funcSMBusBlockProcCall = 0x00008000 // SMBus 2.0 + funcSMBusQuick = 0x00010000 + funcSMBusReadByte = 0x00020000 + funcSMBusWriteByte = 0x00040000 + funcSMBusReadByteData = 0x00080000 + funcSMBusWriteByteData = 0x00100000 + funcSMBusReadWordData = 0x00200000 + funcSMBusWriteWordData = 0x00400000 + funcSMBusProcCall = 0x00800000 + funcSMBusReadBlockData = 0x01000000 + funcSMBusWriteBlockData = 0x02000000 + funcSMBusReadI2CBlock = 0x04000000 // I2C-like block xfer + funcSMBusWriteI2CBlock = 0x08000000 // w/ 1-byte reg. addr. +) + +func (f functionality) String() string { + var out []string + if f&funcI2C != 0 { + out = append(out, "I2C") + } + if f&func10BitAddr != 0 { + out = append(out, "10BIT_ADDR") + } + if f&funcProtocolMangling != 0 { + out = append(out, "PROTOCOL_MANGLING") + } + if f&funcSMBusPEC != 0 { + out = append(out, "SMBUS_PEC") + } + if f&funcNOSTART != 0 { + out = append(out, "NOSTART") + } + if f&funcSMBusBlockProcCall != 0 { + out = append(out, "SMBUS_BLOCK_PROC_CALL") + } + if f&funcSMBusQuick != 0 { + out = append(out, "SMBUS_QUICK") + } + if f&funcSMBusReadByte != 0 { + out = append(out, "SMBUS_READ_BYTE") + } + if f&funcSMBusWriteByte != 0 { + out = append(out, "SMBUS_WRITE_BYTE") + } + if f&funcSMBusReadByteData != 0 { + out = append(out, "SMBUS_READ_BYTE_DATA") + } + if f&funcSMBusWriteByteData != 0 { + out = append(out, "SMBUS_WRITE_BYTE_DATA") + } + if f&funcSMBusReadWordData != 0 { + out = append(out, "SMBUS_READ_WORD_DATA") + } + if f&funcSMBusWriteWordData != 0 { + out = append(out, "SMBUS_WRITE_WORD_DATA") + } + if f&funcSMBusProcCall != 0 { + out = append(out, "SMBUS_PROC_CALL") + } + if f&funcSMBusReadBlockData != 0 { + out = append(out, "SMBUS_READ_BLOCK_DATA") + } + if f&funcSMBusWriteBlockData != 0 { + out = append(out, "SMBUS_WRITE_BLOCK_DATA") + } + if f&funcSMBusReadI2CBlock != 0 { + out = append(out, "SMBUS_READ_I2C_BLOCK") + } + if f&funcSMBusWriteI2CBlock != 0 { + out = append(out, "SMBUS_WRITE_I2C_BLOCK") + } + return strings.Join(out, "|") +} + +type rdwrIoctlData struct { + msgs uintptr // Pointer to i2cMsg + nmsgs uint32 +} + +type i2cMsg struct { + addr uint16 // Address to communicate with + flags uint16 // 1 for read, see i2c.h for more details + length uint16 + buf uintptr +} + +// + +// driverI2C implements periph.Driver. +type driverI2C struct { + mu sync.Mutex + buses []string + setSpeed func(f physic.Frequency) error +} + +func (d *driverI2C) String() string { + return "sysfs-i2c" +} + +func (d *driverI2C) Prerequisites() []string { + return nil +} + +func (d *driverI2C) After() []string { + return nil +} + +func (d *driverI2C) Init() (bool, error) { + // Do not use "/sys/bus/i2c/devices/i2c-" as Raspbian's provided udev rules + // only modify the ACL of /dev/i2c-* but not the ones in /sys/bus/... + prefix := "/dev/i2c-" + items, err := filepath.Glob(prefix + "*") + if err != nil { + return true, err + } + if len(items) == 0 { + return false, errors.New("no I²C bus found") + } + // Make sure they are registered in order. + sort.Strings(items) + for _, item := range items { + bus, err := strconv.Atoi(item[len(prefix):]) + if err != nil { + continue + } + name := fmt.Sprintf("/dev/i2c-%d", bus) + d.buses = append(d.buses, name) + aliases := []string{fmt.Sprintf("I2C%d", bus)} + if err := i2creg.Register(name, aliases, bus, openerI2C(bus).Open); err != nil { + return true, err + } + } + return true, nil +} + +type openerI2C int + +func (o openerI2C) Open() (i2c.BusCloser, error) { + b, err := NewI2C(int(o)) + if err != nil { + return nil, err + } + return b, nil +} + +func init() { + if isLinux { + periph.MustRegister(&drvI2C) + } +} + +var drvI2C driverI2C + +var _ i2c.Bus = &I2C{} +var _ i2c.BusCloser = &I2C{} diff --git a/vendor/periph.io/x/periph/host/sysfs/led.go b/vendor/periph.io/x/periph/host/sysfs/led.go new file mode 100644 index 0000000..f9e22cb --- /dev/null +++ b/vendor/periph.io/x/periph/host/sysfs/led.go @@ -0,0 +1,257 @@ +// Copyright 2016 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 sysfs + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "sort" + "strconv" + "sync" + "time" + + "periph.io/x/periph" + "periph.io/x/periph/conn" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/physic" + "periph.io/x/periph/conn/pin" + "periph.io/x/periph/host/fs" +) + +// LEDs is all the leds discovered on this host via sysfs. +// +// Depending on the user context, the LEDs may be read-only or writeable. +var LEDs []*LED + +// LEDByName returns a *LED for the LED name, if any. +// +// For all practical purpose, a LED is considered an output-only gpio.PinOut. +func LEDByName(name string) (*LED, error) { + // TODO(maruel): Use a bisect or a map. For now we don't expect more than a + // handful of LEDs so it doesn't matter. + for _, led := range LEDs { + if led.name == name { + if err := led.open(); err != nil { + return nil, err + } + return led, nil + } + } + return nil, errors.New("sysfs-led: invalid LED name") +} + +// LED represents one LED on the system. +type LED struct { + number int + name string + root string + + mu sync.Mutex + fBrightness *fs.File // handle to /sys/class/gpio/gpio*/direction; never closed +} + +// String implements conn.Resource. +func (l *LED) String() string { + return fmt.Sprintf("%s(%d)", l.name, l.number) +} + +// Halt implements conn.Resource. +// +// It turns the light off. +func (l *LED) Halt() error { + return l.Out(gpio.Low) +} + +// Name implements pin.Pin. +func (l *LED) Name() string { + return l.name +} + +// Number implements pin.Pin. +func (l *LED) Number() int { + return l.number +} + +// Function implements pin.Pin. +func (l *LED) Function() string { + return string(l.Func()) +} + +// Func implements pin.PinFunc. +func (l *LED) Func() pin.Func { + if l.Read() { + return "LED/On" + } + return "LED/Off" +} + +// SupportedFuncs implements pin.PinFunc. +func (l *LED) SupportedFuncs() []pin.Func { + return []pin.Func{"LED"} +} + +// SetFunc implements pin.PinFunc. +func (l *LED) SetFunc(f pin.Func) error { + return errors.New("sysfs-led: not implemented") +} + +// In implements gpio.PinIn. +func (l *LED) In(pull gpio.Pull, edge gpio.Edge) error { + if pull != gpio.Float && pull != gpio.PullNoChange { + return errors.New("sysfs-led: pull is not supported on LED") + } + if edge != gpio.NoEdge { + return errors.New("sysfs-led: edge is not supported on LED") + } + return nil +} + +// Read implements gpio.PinIn. +func (l *LED) Read() gpio.Level { + err := l.open() + if err != nil { + return gpio.Low + } + l.mu.Lock() + defer l.mu.Unlock() + if _, err := l.fBrightness.Seek(0, 0); err != nil { + return gpio.Low + } + var b [4]byte + if _, err := l.fBrightness.Read(b[:]); err != nil { + return gpio.Low + } + if b[0] != '0' { + return gpio.High + } + return gpio.Low +} + +// WaitForEdge implements gpio.PinIn. +func (l *LED) WaitForEdge(timeout time.Duration) bool { + return false +} + +// Pull implements gpio.PinIn. +func (l *LED) Pull() gpio.Pull { + return gpio.PullNoChange +} + +// DefaultPull implements gpio.PinIn. +func (l *LED) DefaultPull() gpio.Pull { + return gpio.PullNoChange +} + +// Out implements gpio.PinOut. +func (l *LED) Out(level gpio.Level) error { + err := l.open() + if err != nil { + return err + } + l.mu.Lock() + defer l.mu.Unlock() + if _, err = l.fBrightness.Seek(0, 0); err != nil { + return err + } + if level { + _, err = l.fBrightness.Write([]byte("255")) + } else { + _, err = l.fBrightness.Write([]byte("0")) + } + return err +} + +// PWM implements gpio.PinOut. +// +// This sets the intensity level, if supported. The frequency is ignored. +func (l *LED) PWM(d gpio.Duty, f physic.Frequency) error { + err := l.open() + if err != nil { + return err + } + l.mu.Lock() + defer l.mu.Unlock() + if _, err = l.fBrightness.Seek(0, 0); err != nil { + return err + } + v := (d + gpio.DutyMax/512) / (gpio.DutyMax / 256) + _, err = l.fBrightness.Write([]byte(strconv.Itoa(int(v)))) + return err +} + +// + +func (l *LED) open() error { + l.mu.Lock() + defer l.mu.Unlock() + // trigger, max_brightness. + var err error + if l.fBrightness == nil { + p := l.root + "brightness" + if l.fBrightness, err = fs.Open(p, os.O_RDWR); err != nil { + // Retry with read-only. This is the default setting. + l.fBrightness, err = fs.Open(p, os.O_RDONLY) + } + } + return err +} + +// driverLED implements periph.Driver. +type driverLED struct { +} + +func (d *driverLED) String() string { + return "sysfs-led" +} + +func (d *driverLED) Prerequisites() []string { + return nil +} + +func (d *driverLED) After() []string { + return nil +} + +// Init initializes LEDs sysfs handling code. +// +// Uses led sysfs as described* at +// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-led +// +// * for the most minimalistic meaning of 'described'. +func (d *driverLED) Init() (bool, error) { + items, err := filepath.Glob("/sys/class/leds/*") + if err != nil { + return true, err + } + if len(items) == 0 { + return false, errors.New("no LED found") + } + // This make the LEDs in deterministic order. + sort.Strings(items) + for i, item := range items { + LEDs = append(LEDs, &LED{ + number: i, + name: filepath.Base(item), + root: item + "/", + }) + } + return true, nil +} + +func init() { + if isLinux { + periph.MustRegister(&drvLED) + } +} + +var drvLED driverLED + +var _ conn.Resource = &LED{} +var _ gpio.PinIn = &LED{} +var _ gpio.PinOut = &LED{} +var _ gpio.PinIO = &LED{} +var _ pin.PinFunc = &LED{} diff --git a/vendor/periph.io/x/periph/host/sysfs/spi.go b/vendor/periph.io/x/periph/host/sysfs/spi.go new file mode 100644 index 0000000..9eb89cb --- /dev/null +++ b/vendor/periph.io/x/periph/host/sysfs/spi.go @@ -0,0 +1,622 @@ +// Copyright 2016 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 sysfs + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "sync" + "unsafe" + + "periph.io/x/periph" + "periph.io/x/periph/conn" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/gpio/gpioreg" + "periph.io/x/periph/conn/physic" + "periph.io/x/periph/conn/spi" + "periph.io/x/periph/conn/spi/spireg" + "periph.io/x/periph/host/fs" +) + +// NewSPI opens a SPI port via its devfs interface as described at +// https://www.kernel.org/doc/Documentation/spi/spidev and +// https://www.kernel.org/doc/Documentation/spi/spi-summary +// +// The resulting object is safe for concurrent use. +// +// busNumber is the bus number as exported by devfs. For example if the path is +// /dev/spidev0.1, busNumber should be 0 and chipSelect should be 1. +// +// It is recommended to use https://periph.io/x/periph/conn/spi/spireg#Open +// instead of using NewSPI() directly as the package sysfs is providing a +// Linux-specific implementation. periph.io works on many OSes! This permits +// it to work on all operating systems, or devices like SPI over USB. +func NewSPI(busNumber, chipSelect int) (*SPI, error) { + if isLinux { + return newSPI(busNumber, chipSelect) + } + return nil, errors.New("sysfs-spi: not implemented on non-linux OSes") +} + +// SPI is an open SPI port. +type SPI struct { + conn spiConn +} + +// Close closes the handle to the SPI driver. It is not a requirement to close +// before process termination. +// +// Note that the object is not reusable afterward. +func (s *SPI) Close() error { + s.conn.mu.Lock() + defer s.conn.mu.Unlock() + if err := s.conn.f.Close(); err != nil { + return fmt.Errorf("sysfs-spi: %v", err) + } + s.conn.f = nil + return nil +} + +func (s *SPI) String() string { + return s.conn.String() +} + +// LimitSpeed implements spi.ConnCloser. +func (s *SPI) LimitSpeed(f physic.Frequency) error { + if f > physic.GigaHertz { + return fmt.Errorf("sysfs-spi: invalid speed %s; maximum supported clock is 1GHz", f) + } + if f < 100*physic.Hertz { + return fmt.Errorf("sysfs-spi: invalid speed %s; minimum supported clock is 100Hz; did you forget to multiply by physic.MegaHertz?", f) + } + s.conn.mu.Lock() + defer s.conn.mu.Unlock() + s.conn.freqPort = f + return nil +} + +// Connect implements spi.Port. +// +// It must be called before any I/O. +func (s *SPI) Connect(f physic.Frequency, mode spi.Mode, bits int) (spi.Conn, error) { + if f > physic.GigaHertz { + return nil, fmt.Errorf("sysfs-spi: invalid speed %s; maximum supported clock is 1GHz", f) + } + if f < 100*physic.Hertz { + return nil, fmt.Errorf("sysfs-spi: invalid speed %s; minimum supported clock is 100Hz; did you forget to multiply by physic.MegaHertz?", f) + } + if mode&^(spi.Mode3|spi.HalfDuplex|spi.NoCS|spi.LSBFirst) != 0 { + return nil, fmt.Errorf("sysfs-spi: invalid mode %v", mode) + } + if bits < 1 || bits >= 256 { + return nil, fmt.Errorf("sysfs-spi: invalid bits %d", bits) + } + s.conn.mu.Lock() + defer s.conn.mu.Unlock() + if s.conn.connected { + return nil, errors.New("sysfs-spi: Connect() can only be called exactly once") + } + s.conn.connected = true + s.conn.freqConn = f + s.conn.bitsPerWord = uint8(bits) + // Only mode needs to be set via an IOCTL, others can be specified in the + // spiIOCTransfer packet, which saves a kernel call. + m := mode & spi.Mode3 + s.conn.muPins.Lock() + { + if mode&spi.HalfDuplex != 0 { + m |= threeWire + s.conn.halfDuplex = true + // In case initPins() had been called before Connect(). + s.conn.mosi = gpio.INVALID + } + if mode&spi.NoCS != 0 { + m |= noCS + s.conn.noCS = true + // In case initPins() had been called before Connect(). + s.conn.cs = gpio.INVALID + } + } + s.conn.muPins.Unlock() + if mode&spi.LSBFirst != 0 { + m |= lSBFirst + } + // Only the first 8 bits are used. This only works because the system is + // running in little endian. + if err := s.conn.setFlag(spiIOCMode, uint64(m)); err != nil { + return nil, fmt.Errorf("sysfs-spi: setting mode %v failed: %v", mode, err) + } + return &s.conn, nil +} + +// MaxTxSize implements conn.Limits +func (s *SPI) MaxTxSize() int { + return drvSPI.bufSize +} + +// CLK implements spi.Pins. +func (s *SPI) CLK() gpio.PinOut { + return s.conn.CLK() +} + +// MISO implements spi.Pins. +func (s *SPI) MISO() gpio.PinIn { + return s.conn.MISO() +} + +// MOSI implements spi.Pins. +func (s *SPI) MOSI() gpio.PinOut { + return s.conn.MOSI() +} + +// CS implements spi.Pins. +func (s *SPI) CS() gpio.PinOut { + return s.conn.CS() +} + +// Private details. + +func newSPI(busNumber, chipSelect int) (*SPI, error) { + if busNumber < 0 || busNumber >= 1<<16 { + return nil, fmt.Errorf("sysfs-spi: invalid bus %d", busNumber) + } + if chipSelect < 0 || chipSelect > 255 { + return nil, fmt.Errorf("sysfs-spi: invalid chip select %d", chipSelect) + } + // Use the devfs path for now. + f, err := ioctlOpen(fmt.Sprintf("/dev/spidev%d.%d", busNumber, chipSelect), os.O_RDWR) + if err != nil { + return nil, fmt.Errorf("sysfs-spi: %v", err) + } + return &SPI{ + spiConn{ + name: fmt.Sprintf("SPI%d.%d", busNumber, chipSelect), + f: f, + busNumber: busNumber, + chipSelect: chipSelect, + }, + }, nil +} + +// + +// spiConn implements spi.Conn. +type spiConn struct { + // Immutable + name string + f ioctlCloser + busNumber int + chipSelect int + + mu sync.Mutex + freqPort physic.Frequency // Frequency specified at LimitSpeed() + freqConn physic.Frequency // Frequency specified at Connect() + bitsPerWord uint8 + connected bool + halfDuplex bool + noCS bool + // Heap optimization: reduce the amount of memory allocations during + // transactions. + io [4]spiIOCTransfer + p [2]spi.Packet + + // Use a separate lock for the pins, so that they can be queried while a + // transaction is happening. + muPins sync.Mutex + clk gpio.PinOut + mosi gpio.PinOut + miso gpio.PinIn + cs gpio.PinOut +} + +func (s *spiConn) String() string { + return s.name +} + +// Read implements io.Reader. +func (s *spiConn) Read(b []byte) (int, error) { + if len(b) == 0 { + return 0, errors.New("sysfs-spi: Read() with empty buffer") + } + if drvSPI.bufSize != 0 && len(b) > drvSPI.bufSize { + return 0, fmt.Errorf("sysfs-spi: maximum Read length is %d, got %d bytes", drvSPI.bufSize, len(b)) + } + s.mu.Lock() + defer s.mu.Unlock() + s.p[0].W = nil + s.p[0].R = b + if err := s.txPackets(s.p[:1]); err != nil { + return 0, fmt.Errorf("sysfs-spi: Read() failed: %v", err) + } + return len(b), nil +} + +// Write implements io.Writer. +func (s *spiConn) Write(b []byte) (int, error) { + if len(b) == 0 { + return 0, errors.New("sysfs-spi: Write() with empty buffer") + } + if drvSPI.bufSize != 0 && len(b) > drvSPI.bufSize { + return 0, fmt.Errorf("sysfs-spi: maximum Write length is %d, got %d bytes", drvSPI.bufSize, len(b)) + } + s.mu.Lock() + defer s.mu.Unlock() + s.p[0].W = b + s.p[0].R = nil + if err := s.txPackets(s.p[:1]); err != nil { + return 0, fmt.Errorf("sysfs-spi: Write() failed: %v", err) + } + return len(b), nil +} + +// Tx sends and receives data simultaneously. +// +// It is OK if both w and r point to the same underlying byte slice. +// +// spidev enforces the maximum limit of transaction size. It can be as low as +// 4096 bytes. See the platform documentation to learn how to increase the +// limit. +func (s *spiConn) Tx(w, r []byte) error { + l := len(w) + if l == 0 { + if l = len(r); l == 0 { + return errors.New("sysfs-spi: Tx() with empty buffers") + } + } else { + // It's not a big deal to read halfDuplex without the lock. + if !s.halfDuplex && len(r) != 0 && len(r) != len(w) { + return fmt.Errorf("sysfs-spi: Tx(): when both w and r are used, they must be the same size; got %d and %d bytes", len(w), len(r)) + } + } + if drvSPI.bufSize != 0 && l > drvSPI.bufSize { + return fmt.Errorf("sysfs-spi: maximum Tx length is %d, got %d bytes", drvSPI.bufSize, l) + } + s.mu.Lock() + defer s.mu.Unlock() + s.p[0].W = w + s.p[0].R = r + p := s.p[:1] + if s.halfDuplex && len(w) != 0 && len(r) != 0 { + // Create two packets for HalfDuplex operation: one write then one read. + s.p[0].R = nil + s.p[0].KeepCS = true + s.p[1].W = nil + s.p[1].R = r + s.p[1].KeepCS = false + p = s.p[:2] + } else { + s.p[0].KeepCS = false + } + if err := s.txPackets(p); err != nil { + return fmt.Errorf("sysfs-spi: Tx() failed: %v", err) + } + return nil +} + +// TxPackets sends and receives packets as specified by the user. +// +// spidev enforces the maximum limit of transaction size. It can be as low as +// 4096 bytes. See the platform documentation to learn how to increase the +// limit. +func (s *spiConn) TxPackets(p []spi.Packet) error { + total := 0 + for i := range p { + lW := len(p[i].W) + lR := len(p[i].R) + if lW != lR && lW != 0 && lR != 0 { + return fmt.Errorf("sysfs-spi: when both w and r are used, they must be the same size; got %d and %d bytes", lW, lR) + } + l := lW + if l == 0 { + l = lR + } + total += l + } + if total == 0 { + return errors.New("sysfs-spi: empty packets") + } + if drvSPI.bufSize != 0 && total > drvSPI.bufSize { + return fmt.Errorf("sysfs-spi: maximum TxPackets length is %d, got %d bytes", drvSPI.bufSize, total) + } + + s.mu.Lock() + defer s.mu.Unlock() + if s.halfDuplex { + for i := range p { + if len(p[i].W) != 0 && len(p[i].R) != 0 { + return errors.New("sysfs-spi: can only specify one of w or r when in half duplex") + } + } + } + if err := s.txPackets(p); err != nil { + return fmt.Errorf("sysfs-spi: TxPackets() failed: %v", err) + } + return nil +} + +// Duplex implements conn.Conn. +func (s *spiConn) Duplex() conn.Duplex { + if s.halfDuplex { + return conn.Half + } + return conn.Full +} + +// MaxTxSize implements conn.Limits. +func (s *spiConn) MaxTxSize() int { + return drvSPI.bufSize +} + +// CLK implements spi.Pins. +func (s *spiConn) CLK() gpio.PinOut { + s.initPins() + return s.clk +} + +// MISO implements spi.Pins. +func (s *spiConn) MISO() gpio.PinIn { + s.initPins() + return s.miso +} + +// MOSI implements spi.Pins. +func (s *spiConn) MOSI() gpio.PinOut { + s.initPins() + return s.mosi +} + +// CS implements spi.Pins. +func (s *spiConn) CS() gpio.PinOut { + s.initPins() + return s.cs +} + +// + +func (s *spiConn) txPackets(p []spi.Packet) error { + // Convert the packets. + f := s.freqPort + if s.freqConn != 0 && (s.freqPort == 0 || s.freqConn < s.freqPort) { + f = s.freqConn + } + var m []spiIOCTransfer + if len(p) > len(s.io) { + m = make([]spiIOCTransfer, len(p)) + } else { + m = s.io[:len(p)] + } + for i := range p { + bits := p[i].BitsPerWord + if bits == 0 { + bits = s.bitsPerWord + } + csInvert := false + if !s.noCS { + // Invert CS behavior when a packet has KeepCS false, except for the last + // packet when KeepCS is true. + last := i == len(p)-1 + csInvert = p[i].KeepCS == last + } + m[i].reset(p[i].W, p[i].R, f, bits, csInvert) + } + return s.f.Ioctl(spiIOCTx(len(m)), uintptr(unsafe.Pointer(&m[0]))) +} + +func (s *spiConn) setFlag(op uint, arg uint64) error { + return s.f.Ioctl(op, uintptr(unsafe.Pointer(&arg))) +} + +// GetFlag allows to read back flags set via a ioctl, i.e. setFlag. It is +// exported to allow calling it from the smoke test. +func (s *spiConn) GetFlag(op uint) (arg uint64, err error) { + err = s.f.Ioctl(op, uintptr(unsafe.Pointer(&arg))) + return +} + +func (s *spiConn) initPins() { + s.muPins.Lock() + defer s.muPins.Unlock() + if s.clk != nil { + return + } + if s.clk = gpioreg.ByName(fmt.Sprintf("SPI%d_CLK", s.busNumber)); s.clk == nil { + s.clk = gpio.INVALID + } + if s.miso = gpioreg.ByName(fmt.Sprintf("SPI%d_MISO", s.busNumber)); s.miso == nil { + s.miso = gpio.INVALID + } + // s.mosi is set to INVALID if HalfDuplex was specified. + if s.mosi != gpio.INVALID { + if s.mosi = gpioreg.ByName(fmt.Sprintf("SPI%d_MOSI", s.busNumber)); s.mosi == nil { + s.mosi = gpio.INVALID + } + } + // s.cs is set to INVALID if NoCS was specified. + if s.cs != gpio.INVALID { + if s.cs = gpioreg.ByName(fmt.Sprintf("SPI%d_CS%d", s.busNumber, s.chipSelect)); s.cs == nil { + s.cs = gpio.INVALID + } + } +} + +const ( + cSHigh spi.Mode = 0x4 // CS active high instead of default low (not recommended) + lSBFirst spi.Mode = 0x8 // Use little endian encoding for each word + threeWire spi.Mode = 0x10 // half-duplex; MOSI and MISO are shared + loop spi.Mode = 0x20 // loopback mode + noCS spi.Mode = 0x40 // do not assert CS + ready spi.Mode = 0x80 // slave pulls low to pause + // The driver optionally support dual and quad data lines. +) + +// spidev driver IOCTL control codes. +// +// Constants and structure definition can be found at +// /usr/include/linux/spi/spidev.h. +const spiIOCMagic uint = 'k' + +var ( + spiIOCMode = fs.IOW(spiIOCMagic, 1, 1) // SPI_IOC_WR_MODE (8 bits) + spiIOLSBFirst = fs.IOW(spiIOCMagic, 2, 1) // SPI_IOC_WR_LSB_FIRST + spiIOCBitsPerWord = fs.IOW(spiIOCMagic, 3, 1) // SPI_IOC_WR_BITS_PER_WORD + spiIOCMaxSpeedHz = fs.IOW(spiIOCMagic, 4, 4) // SPI_IOC_WR_MAX_SPEED_HZ + spiIOCMode32 = fs.IOW(spiIOCMagic, 5, 4) // SPI_IOC_WR_MODE32 (32 bits) +) + +// spiIOCTx(l) calculates the equivalent of SPI_IOC_MESSAGE(l) to execute a +// transaction. +func spiIOCTx(l int) uint { + return fs.IOW(spiIOCMagic, 0, uint(l)*32) +} + +// spiIOCTransfer is spi_ioc_transfer in linux/spi/spidev.h. +// +// Also documented as struct spi_transfer at +// https://www.kernel.org/doc/html/latest/driver-api/spi.html +type spiIOCTransfer struct { + tx uint64 // Pointer to byte slice + rx uint64 // Pointer to byte slice + length uint32 // buffer length of tx and rx in bytes + speedHz uint32 // temporarily override the speed + delayUsecs uint16 // µs to sleep before selecting the device before the next transfer + bitsPerWord uint8 // temporarily override the number of bytes per word + csChange uint8 // true to deassert CS before next transfer + txNBits uint8 + rxNBits uint8 + pad uint16 +} + +func (s *spiIOCTransfer) reset(w, r []byte, f physic.Frequency, bitsPerWord uint8, csInvert bool) { + s.tx = 0 + s.rx = 0 + s.length = 0 + // w and r must be the same length. + if l := len(w); l != 0 { + s.tx = uint64(uintptr(unsafe.Pointer(&w[0]))) + s.length = uint32(l) + } + if l := len(r); l != 0 { + s.rx = uint64(uintptr(unsafe.Pointer(&r[0]))) + s.length = uint32(l) + } + s.speedHz = uint32((f + 500*physic.MilliHertz) / physic.Hertz) + s.delayUsecs = 0 + s.bitsPerWord = bitsPerWord + if csInvert { + s.csChange = 1 + } else { + s.csChange = 0 + } + s.txNBits = 0 + s.rxNBits = 0 + s.pad = 0 +} + +// + +// driverSPI implements periph.Driver. +type driverSPI struct { + // bufSize is the maximum number of bytes allowed per I/O on the SPI port. + bufSize int +} + +func (d *driverSPI) String() string { + return "sysfs-spi" +} + +func (d *driverSPI) Prerequisites() []string { + return nil +} + +func (d *driverSPI) After() []string { + return nil +} + +func (d *driverSPI) Init() (bool, error) { + // This driver is only registered on linux, so there is no legitimate time to + // skip it. + + // Do not use "/sys/bus/spi/devices/spi" as Raspbian's provided udev rules + // only modify the ACL of /dev/spidev* but not the ones in /sys/bus/... + prefix := "/dev/spidev" + items, err := filepath.Glob(prefix + "*") + if err != nil { + return true, err + } + if len(items) == 0 { + return false, errors.New("no SPI port found") + } + sort.Strings(items) + for _, item := range items { + parts := strings.Split(item[len(prefix):], ".") + if len(parts) != 2 { + continue + } + bus, err := strconv.Atoi(parts[0]) + if err != nil { + continue + } + cs, err := strconv.Atoi(parts[1]) + if err != nil { + continue + } + name := fmt.Sprintf("/dev/spidev%d.%d", bus, cs) + aliases := []string{fmt.Sprintf("SPI%d.%d", bus, cs)} + n := bus + if cs != 0 { + n = -1 + } + if err := spireg.Register(name, aliases, n, (&openerSPI{bus, cs}).Open); err != nil { + return true, err + } + } + f, err := fs.Open("/sys/module/spidev/parameters/bufsiz", os.O_RDONLY) + if err != nil { + return true, err + } + defer f.Close() + b, err := ioutil.ReadAll(f) + if err != nil { + return true, err + } + // Update the global value. + drvSPI.bufSize, err = strconv.Atoi(strings.TrimSpace(string(b))) + return true, err +} + +type openerSPI struct { + bus int + cs int +} + +func (o *openerSPI) Open() (spi.PortCloser, error) { + return NewSPI(o.bus, o.cs) +} + +func init() { + if isLinux { + periph.MustRegister(&drvSPI) + } +} + +var drvSPI driverSPI + +var _ conn.Limits = &SPI{} +var _ conn.Limits = &spiConn{} +var _ io.Reader = &spiConn{} +var _ io.Writer = &spiConn{} +var _ spi.Conn = &spiConn{} +var _ spi.Pins = &SPI{} +var _ spi.Pins = &spiConn{} +var _ spi.Port = &SPI{} +var _ spi.PortCloser = &SPI{} +var _ fmt.Stringer = &SPI{} diff --git a/vendor/periph.io/x/periph/host/sysfs/sysfs.go b/vendor/periph.io/x/periph/host/sysfs/sysfs.go new file mode 100644 index 0000000..3cfaf49 --- /dev/null +++ b/vendor/periph.io/x/periph/host/sysfs/sysfs.go @@ -0,0 +1,62 @@ +// 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 sysfs + +import ( + "io" + + "periph.io/x/periph/host/fs" +) + +var ioctlOpen = ioctlOpenDefault + +func ioctlOpenDefault(path string, flag int) (ioctlCloser, error) { + f, err := fs.Open(path, flag) + if err != nil { + return nil, err + } + return f, nil +} + +var fileIOOpen = fileIOOpenDefault + +func fileIOOpenDefault(path string, flag int) (fileIO, error) { + f, err := fs.Open(path, flag) + if err != nil { + return nil, err + } + return f, nil +} + +type ioctlCloser interface { + io.Closer + fs.Ioctler +} + +type fileIO interface { + Fd() uintptr + fs.Ioctler + io.Closer + io.Reader + io.Seeker + io.Writer +} + +// seekRead seeks to the beginning of a file and reads it. +func seekRead(f fileIO, b []byte) (int, error) { + if _, err := f.Seek(0, 0); err != nil { + return 0, err + } + return f.Read(b) +} + +// seekWrite seeks to the beginning of a file and writes to it. +func seekWrite(f fileIO, b []byte) error { + if _, err := f.Seek(0, 0); err != nil { + return err + } + _, err := f.Write(b) + return err +} diff --git a/vendor/periph.io/x/periph/host/sysfs/sysfs_linux.go b/vendor/periph.io/x/periph/host/sysfs/sysfs_linux.go new file mode 100644 index 0000000..735ca80 --- /dev/null +++ b/vendor/periph.io/x/periph/host/sysfs/sysfs_linux.go @@ -0,0 +1,17 @@ +// Copyright 2016 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 sysfs + +import ( + "os" + "syscall" +) + +const isLinux = true + +func isErrBusy(err error) bool { + e, ok := err.(*os.PathError) + return ok && e.Err == syscall.EBUSY +} diff --git a/vendor/periph.io/x/periph/host/sysfs/sysfs_other.go b/vendor/periph.io/x/periph/host/sysfs/sysfs_other.go new file mode 100644 index 0000000..3cdacd0 --- /dev/null +++ b/vendor/periph.io/x/periph/host/sysfs/sysfs_other.go @@ -0,0 +1,14 @@ +// Copyright 2016 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. + +// +build !linux + +package sysfs + +const isLinux = false + +func isErrBusy(err error) bool { + // This function is not used on non-linux. + return false +} diff --git a/vendor/periph.io/x/periph/host/sysfs/thermal_sensor.go b/vendor/periph.io/x/periph/host/sysfs/thermal_sensor.go new file mode 100644 index 0000000..c3d7d0b --- /dev/null +++ b/vendor/periph.io/x/periph/host/sysfs/thermal_sensor.go @@ -0,0 +1,261 @@ +// Copyright 2016 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 sysfs + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "sort" + "strconv" + "sync" + "time" + + "periph.io/x/periph" + "periph.io/x/periph/conn" + "periph.io/x/periph/conn/physic" +) + +// ThermalSensors is all the sensors discovered on this host via sysfs. It +// includes 'thermal' devices as well as temperature 'hwmon' devices, so +// pre-configured onewire temperature sensors will be discovered automatically. +var ThermalSensors []*ThermalSensor + +// ThermalSensorByName returns a *ThermalSensor for the sensor name, if any. +func ThermalSensorByName(name string) (*ThermalSensor, error) { + // TODO(maruel): Use a bisect or a map. For now we don't expect more than a + // handful of thermal sensors so it doesn't matter. + for _, t := range ThermalSensors { + if t.name == name { + if err := t.open(); err != nil { + return nil, err + } + return t, nil + } + } + return nil, errors.New("sysfs-thermal: invalid sensor name") +} + +// ThermalSensor represents one thermal sensor on the system. +type ThermalSensor struct { + name string + root string + sensorFilename string + typeFilename string + + mu sync.Mutex + nameType string + f fileIO + precision physic.Temperature + + done chan struct{} +} + +func (t *ThermalSensor) String() string { + return t.name +} + +// Halt stops a continuous sense that was started with SenseContinuous. +func (t *ThermalSensor) Halt() error { + t.mu.Lock() + defer t.mu.Unlock() + if t.done != nil { + close(t.done) + t.done = nil + } + return nil +} + +// Type returns the type of sensor as exported by sysfs. +func (t *ThermalSensor) Type() string { + t.mu.Lock() + defer t.mu.Unlock() + if t.nameType == "" { + nameType, err := t.readType() + if err != nil { + return err.Error() + } + t.nameType = nameType + } + return t.nameType +} + +func (t *ThermalSensor) readType() (string, error) { + f, err := fileIOOpen(t.root+t.typeFilename, os.O_RDONLY) + if os.IsNotExist(err) { + return "", nil + } + if err != nil { + return "", fmt.Errorf("sysfs-thermal: %v", err) + } + defer f.Close() + var buf [256]byte + n, err := f.Read(buf[:]) + if err != nil { + return "", fmt.Errorf("sysfs-thermal: %v", err) + } + if n < 2 { + return "", nil + } + return string(buf[:n-1]), nil +} + +// Sense implements physic.SenseEnv. +func (t *ThermalSensor) Sense(e *physic.Env) error { + if err := t.open(); err != nil { + return err + } + t.mu.Lock() + defer t.mu.Unlock() + var buf [24]byte + n, err := seekRead(t.f, buf[:]) + if err != nil { + return fmt.Errorf("sysfs-thermal: %v", err) + } + if n < 2 { + return errors.New("sysfs-thermal: failed to read temperature") + } + i, err := strconv.Atoi(string(buf[:n-1])) + if err != nil { + return fmt.Errorf("sysfs-thermal: %v", err) + } + if t.precision == 0 { + t.precision = physic.MilliKelvin + if i < 100 { + t.precision *= 1000 + } + } + e.Temperature = physic.Temperature(i)*t.precision + physic.ZeroCelsius + return nil +} + +// SenseContinuous implements physic.SenseEnv. +func (t *ThermalSensor) SenseContinuous(interval time.Duration) (<-chan physic.Env, error) { + t.mu.Lock() + defer t.mu.Unlock() + if t.done != nil { + return nil, nil + } + done := make(chan struct{}) + ret := make(chan physic.Env) + ticker := time.NewTicker(interval) + + go func() { + defer ticker.Stop() + for { + select { + case <-done: + close(ret) + return + case <-ticker.C: + var e physic.Env + if err := t.Sense(&e); err == nil { + ret <- e + } + } + } + }() + + t.done = done + return ret, nil +} + +// Precision implements physic.SenseEnv. +func (t *ThermalSensor) Precision(e *physic.Env) { + if t.precision == 0 { + dummy := physic.Env{} + // Ignore the error. + _ = t.Sense(&dummy) + } + t.mu.Lock() + defer t.mu.Unlock() + e.Temperature = t.precision +} + +// + +func (t *ThermalSensor) open() error { + t.mu.Lock() + defer t.mu.Unlock() + if t.f != nil { + return nil + } + f, err := fileIOOpen(t.root+t.sensorFilename, os.O_RDONLY) + if err != nil { + return fmt.Errorf("sysfs-thermal: %v", err) + } + t.f = f + return nil +} + +// driverThermalSensor implements periph.Driver. +type driverThermalSensor struct { +} + +func (d *driverThermalSensor) String() string { + return "sysfs-thermal" +} + +func (d *driverThermalSensor) Prerequisites() []string { + return nil +} + +func (d *driverThermalSensor) After() []string { + return nil +} + +// Init initializes thermal sysfs handling code. +// +// Uses sysfs as described* at +// https://www.kernel.org/doc/Documentation/thermal/sysfs-api.txt +// +// * for the most minimalistic meaning of 'described'. +func (d *driverThermalSensor) Init() (bool, error) { + if err := d.discoverDevices("/sys/class/thermal/*/temp", "type"); err != nil { + return true, err + } + if err := d.discoverDevices("/sys/class/hwmon/*/temp*_input", "device/name"); err != nil { + return true, err + } + if len(ThermalSensors) == 0 { + return false, errors.New("sysfs-thermal: no sensor found") + } + return true, nil +} + +func (d *driverThermalSensor) discoverDevices(glob, typeFilename string) error { + // This driver is only registered on linux, so there is no legitimate time to + // skip it. + items, err := filepath.Glob(glob) + if err != nil { + return err + } + if len(items) == 0 { + return nil + } + sort.Strings(items) + for _, item := range items { + base := filepath.Dir(item) + ThermalSensors = append(ThermalSensors, &ThermalSensor{ + name: filepath.Base(base), + root: base + "/", + sensorFilename: filepath.Base(item), + typeFilename: typeFilename, + }) + } + return nil +} + +func init() { + if isLinux { + periph.MustRegister(&drvThermalSensor) + } +} + +var drvThermalSensor driverThermalSensor + +var _ conn.Resource = &ThermalSensor{} +var _ physic.SenseEnv = &ThermalSensor{} diff --git a/vendor/periph.io/x/periph/host/videocore/videocore.go b/vendor/periph.io/x/periph/host/videocore/videocore.go new file mode 100644 index 0000000..3dcea0d --- /dev/null +++ b/vendor/periph.io/x/periph/host/videocore/videocore.go @@ -0,0 +1,229 @@ +// Copyright 2016 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 videocore interacts with the VideoCore GPU found on bcm283x. +// +// This package shouldn't be used directly, it is used by bcm283x's DMA +// implementation. +// +// Datasheet +// +// While not an actual datasheet, this is the closest to actual formal +// documentation: +// https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface +package videocore + +import ( + "fmt" + "os" + "sync" + "unsafe" + + "periph.io/x/periph/host/fs" + "periph.io/x/periph/host/pmem" +) + +// Mem represents contiguous physically locked memory that was allocated by +// VideoCore. +// +// The memory is mapped in user space. +type Mem struct { + *pmem.View + handle uint32 +} + +// Close unmaps the physical memory allocation. +// +// It is important to call this function otherwise the memory is locked until +// the host reboots. +func (m *Mem) Close() error { + if err := m.View.Close(); err != nil { + return wrapf("failed to close physical view: %v", err) + } + if _, err := mailboxTx32(mbUnlockMemory, m.handle); err != nil { + return wrapf("failed to unlock memory: %v", err) + } + if _, err := mailboxTx32(mbReleaseMemory, m.handle); err != nil { + return wrapf("failed to release memory: %v", err) + } + return nil +} + +// Alloc allocates a continuous chunk of physical memory for use with DMA +// controller. +// +// Size must be rounded to 4Kb. +func Alloc(size int) (*Mem, error) { + if size <= 0 { + return nil, wrapf("memory size must be > 0; got %d", size) + } + if size&0xFFF != 0 { + return nil, wrapf("memory size must be rounded to 4096 pages; got %d", size) + } + if err := openMailbox(); err != nil { + return nil, wrapf("failed to open the mailbox to the GPU: %v", err) + } + // Size, Alignment, Flags; returns an opaque handle to be used to release the + // memory. + handle, err := mailboxTx32(mbAllocateMemory, uint32(size), 4096, flagDirect) + if err != nil { + return nil, wrapf("failed request to allocate memory: %v", err) + } + if handle == 0 { + return nil, wrapf("failed to allocate %d bytes", size) + } + // Lock the memory to retrieve a physical memory address. + p, err := mailboxTx32(mbLockMemory, handle) + if err != nil { + return nil, wrapf("failed request to lock memory: %v", err) + } + if p == 0 { + return nil, wrapf("failed to lock memory") + } + b, err := pmem.Map(uint64(p&^0xC0000000), size) + if err != nil { + return nil, wrapf("failed to memory map phyisical pages: %v", err) + } + return &Mem{View: b, handle: handle}, nil +} + +// + +var ( + mu sync.Mutex + mailbox messager + mailboxErr error + + mbIoctl = fs.IOWR('d', 0, uint(unsafe.Sizeof(new(byte)))) +) + +const ( + // All of these return anything but zero (‽) + mbFirmwareVersion = 0x1 // 0, 4 + mbBoardModel = 0x10001 // 0, 4 + mbBoardRevision = 0x10002 // 0, 4 + mbBoardMAC = 0x10003 // 0, 6 + mbBoardSerial = 0x10004 // 0, 8 + mbARMMemory = 0x10005 // 0, 8 + mbVCMemory = 0x10006 // 0, 8 + mbClocks = 0x10007 // 0, variable + // These work: + mbAllocateMemory = 0x3000C // 12, 4 + mbLockMemory = 0x3000D // 4, 4 + mbUnlockMemory = 0x3000E // 4, 4 + mbReleaseMemory = 0x3000F // 4, 4 + mbReply = 0x80000000 // High bit means a reply + + flagDiscardable = 1 << 0 // Can be resized to 0 at any time. Use for cached data. + flagNormal = 0 << 2 // Normal allocating alias. Don't use from ARM. + flagDirect = 1 << 2 // 0xCxxxxxxx Uncached + flagCoherent = 2 << 2 // 0x8xxxxxxx Non-allocating in L2 but coherent + flagL1Nonallocating = flagDirect | flagCoherent // Allocating in L2 + flagZero = 1 << 4 // Initialise buffer to all zeros + flagNoInit = 1 << 5 // Don't initialise (default is initialise to all ones + flagHintPermalock = 1 << 6 // Likely to be locked for long periods of time +) + +type messager interface { + sendMessage(b []uint32) error +} + +type messageBox struct { + f *fs.File +} + +func (m *messageBox) sendMessage(b []uint32) error { + return m.f.Ioctl(mbIoctl, uintptr(unsafe.Pointer(&b[0]))) +} + +func openMailbox() error { + mu.Lock() + defer mu.Unlock() + if mailbox != nil || mailboxErr != nil { + return mailboxErr + } + f, err := fs.Open("/dev/vcio", os.O_RDWR|os.O_SYNC) + mailboxErr = err + if err == nil { + mailbox = &messageBox{f} + mailboxErr = smokeTest() + } + return mailboxErr +} + +// genPacket creates a message to be sent to the GPU via the "mailbox". +// +// The message must be 16-byte aligned because only the upper 28 bits are +// passed; the lower bits are used to select the channel. +func genPacket(cmd uint32, replyLen uint32, args ...uint32) []uint32 { + p := make([]uint32, 48) + offset := uintptr(unsafe.Pointer(&p[0])) & 15 + b := p[16-offset : 32+16-offset] + max := uint32(len(args) * 4) + if replyLen > max { + max = replyLen + } + max = ((max + 3) / 4) * 4 + // size + zero + cmd + in + out + + zero + b[0] = uint32(6*4) + max // message total length in bytes, including trailing zero + b[2] = cmd // + b[3] = uint32(len(args)) * 4 // inputs length in bytes + b[4] = replyLen // outputs length in bytes + copy(b[5:], args) + return b[:6+max/4] +} + +func sendPacket(b []uint32) error { + if err := mailbox.sendMessage(b); err != nil { + return fmt.Errorf("failed to send IOCTL: %v", err) + } + if b[1] != mbReply { + // 0x80000001 means partial response. + return fmt.Errorf("got unexpected reply bit 0x%08x", b[1]) + } + return nil +} + +func mailboxTx32(cmd uint32, args ...uint32) (uint32, error) { + b := genPacket(cmd, 4, args...) + if err := sendPacket(b); err != nil { + return 0, err + } + if b[4] != mbReply|4 { + return 0, fmt.Errorf("got unexpected reply size 0x%08x", b[4]) + } + return b[5], nil +} + +/* +// mailboxTx is the generic version of mailboxTx32. It is not currently needed. +func mailboxTx(cmd uint32, reply []byte, args ...uint32) error { + b := genPacket(cmd, uint32(len(reply)), args...) + if err := sendPacket(b); err != nil { + return err + } + rep := b[4] + if rep&mbReply == 0 { + return fmt.Errorf("got unexpected reply size 0x%08x", b[4]) + } + rep &^= mbReply + if rep == 0 || rep > uint32(len(reply)) { + return fmt.Errorf("got unexpected reply size 0x%08x", b[4]) + } + return nil +} +*/ + +func smokeTest() error { + // It returns 0 on a RPi3 but don't assert this in case the VC firmware gets + // updated. + _, err := mailboxTx32(mbFirmwareVersion) + return err +} + +func wrapf(format string, a ...interface{}) error { + return fmt.Errorf("videocore: "+format, a...) +} + +var _ pmem.Mem = &Mem{} diff --git a/vendor/periph.io/x/periph/periph.go b/vendor/periph.io/x/periph/periph.go new file mode 100644 index 0000000..d4e3b2c --- /dev/null +++ b/vendor/periph.io/x/periph/periph.go @@ -0,0 +1,298 @@ +// Copyright 2016 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 periph is a peripheral I/O library. +// +// Package periph acts as a registry of drivers. It is focused on providing +// high quality host drivers that provide high-speed access to the hardware on +// the host computer itself. +// +// To learn more about the goals and design, visit https://periph.io/ +// +// Every device driver should register itself in its package init() function by +// calling periph.MustRegister(). +// +// User shall call either host.Init() or hostextra.Init() on startup to +// initialize all the registered drivers. +// +// Cmd +// +// cmd/ contains executable tools to communicate directly with the devices or +// the buses. +// +// cmd/ is allowed to import from conn/, devices/ and host/. +// +// Conn +// +// conn/ contains interfaces and registries for all the supported protocols and +// connections (I²C, SPI, GPIO, etc). +// +// conn/ is not allowed to import from any other package. +// +// Devices +// +// devices/ contains devices drivers that are connected to bus, port or +// connection (i.e I²C, SPI, 1-wire, GPIO) that can be controlled by the host, +// i.e. ssd1306 (display controller), bm280 (environmental sensor), etc. +// +// devices/ is allowed to import from conn/ and host/. +// +// Experimental +// +// experimental/ contains the drivers that are in the experimental area, not +// yet considered stable. See +// https://periph.io/project/#driver-lifetime-management for the process to +// move drivers out of this area. +// +// experimental/ is allowed to import from conn/, devices/ and host/. +// +// Host +// +// host/ contains all the implementations relating to the host itself, the CPU +// and buses that are exposed by the host onto which devices can be connected, +// i.e. I²C, SPI, GPIO, etc. +// +// host/ is allowed to import from conn/ only. +package periph // import "periph.io/x/periph" + +import ( + "errors" + "strconv" + "strings" + "sync" +) + +// Driver is an implementation for a protocol. +type Driver interface { + // String returns the name of the driver, as to be presented to the user. + // + // It must be unique in the list of registered drivers. + String() string + // Prerequisites returns a list of drivers that must be successfully loaded + // first before attempting to load this driver. + // + // A driver listing a prerequisite not registered is a fatal failure at + // initialization time. + Prerequisites() []string + // After returns a list of drivers that must be loaded first before + // attempting to load this driver. + // + // Unlike Prerequisites(), this driver will still be attempted even if the + // listed driver is missing or failed to load. + // + // This permits serialization without hard requirement. + After() []string + // Init initializes the driver. + // + // A driver may enter one of the three following state: loaded successfully, + // was skipped as irrelevant on this host, failed to load. + // + // On success, it must return true, nil. + // + // When irrelevant (skipped), it must return false, errors.New(). + // + // On failure, it must return true, errors.New(). The failure must + // state why it failed, for example an expected OS provided driver couldn't + // be opened, e.g. /dev/gpiomem on Raspbian. + Init() (bool, error) +} + +// DriverFailure is a driver that wasn't loaded, either because it was skipped +// or because it failed to load. +type DriverFailure struct { + D Driver + Err error +} + +func (d DriverFailure) String() string { + out := d.D.String() + ": " + if d.Err != nil { + out += d.Err.Error() + } else { + out += "" + } + return out +} + +// State is the state of loaded device drivers. +// +// Each list is sorted by the driver name. +type State struct { + Loaded []Driver + Skipped []DriverFailure + Failed []DriverFailure +} + +// Init initialises all the relevant drivers. +// +// Drivers are started concurrently. +// +// It is safe to call this function multiple times, the previous state is +// returned on later calls. +// +// Users will want to use host.Init(), which guarantees a baseline of included +// host drivers. +func Init() (*State, error) { + mu.Lock() + defer mu.Unlock() + if state != nil { + return state, nil + } + return initImpl() +} + +// Register registers a driver to be initialized automatically on Init(). +// +// The d.String() value must be unique across all registered drivers. +// +// It is an error to call Register() after Init() was called. +func Register(d Driver) error { + mu.Lock() + defer mu.Unlock() + if state != nil { + return errors.New("periph: can't call Register() after Init()") + } + + n := d.String() + if _, ok := byName[n]; ok { + return errors.New("periph: driver with same name " + strconv.Quote(n) + " was already registered") + } + byName[n] = d + return nil +} + +// MustRegister calls Register() and panics if registration fails. +// +// This is the function to call in a driver's package init() function. +func MustRegister(d Driver) { + if err := Register(d); err != nil { + panic(err) + } +} + +// + +var ( + // mu guards byName and state. + // - byName is only mutated by Register(). + // - state is only mutated by Init(). + // + // Once Init() is called, Register() refuses registering more drivers, thus + // byName is immutable once Init() started. + mu sync.Mutex + byName = map[string]Driver{} + state *State +) + +// stage is a set of drivers that can be loaded in parallel. +type stage struct { + // Subset of byName drivers, for the ones in this stage. + drvs map[string]Driver +} + +// explodeStages creates one or multiple stages by processing byName. +// +// It searches if there's any driver than has dependency on another driver and +// create stages from this DAG. +// +// It also verifies that there is not cycle in the DAG. +// +// When this function starts, allDriver and byName are guaranteed to be +// immutable. state must not be touched by this function. +func explodeStages() ([]*stage, error) { + // First, create the DAG. + dag := map[string]map[string]struct{}{} + for name, d := range byName { + m := map[string]struct{}{} + for _, p := range d.Prerequisites() { + if _, ok := byName[p]; !ok { + return nil, errors.New("periph: unsatisfied dependency " + strconv.Quote(name) + "->" + strconv.Quote(p) + "; it is missing; skipping") + } + m[p] = struct{}{} + } + for _, p := range d.After() { + // Skip undefined drivers silently, unlike Prerequisites(). + if _, ok := byName[p]; ok { + m[p] = struct{}{} + } + } + dag[name] = m + } + + // Create stages. + var stages []*stage + for len(dag) != 0 { + s := &stage{drvs: map[string]Driver{}} + for name, deps := range dag { + // This driver has no dependency, add it to the current stage. + if len(deps) == 0 { + s.drvs[name] = byName[name] + delete(dag, name) + } + } + if len(s.drvs) == 0 { + // Print out the remaining DAG so users can diagnose. + // It'd probably be nicer if it were done in Register()? + s := make([]string, 0, len(dag)) + for name, deps := range dag { + x := make([]string, 0, len(deps)) + for d := range deps { + x = insertString(x, d) + } + s = insertString(s, name+": "+strings.Join(x, ", ")) + } + return nil, errors.New("periph: found cycle(s) in drivers dependencies:\n" + strings.Join(s, "\n")) + } + stages = append(stages, s) + + // Trim the dependencies for the items remaining in the dag. + for passed := range s.drvs { + for name := range dag { + delete(dag[name], passed) + } + } + } + return stages, nil +} + +func insertDriver(l []Driver, d Driver) []Driver { + n := d.String() + i := search(len(l), func(i int) bool { return l[i].String() > n }) + l = append(l, nil) + copy(l[i+1:], l[i:]) + l[i] = d + return l +} + +func insertDriverFailure(l []DriverFailure, f DriverFailure) []DriverFailure { + n := f.String() + i := search(len(l), func(i int) bool { return l[i].String() > n }) + l = append(l, DriverFailure{}) + copy(l[i+1:], l[i:]) + l[i] = f + return l +} + +func insertString(l []string, s string) []string { + i := search(len(l), func(i int) bool { return l[i] > s }) + l = append(l, "") + copy(l[i+1:], l[i:]) + l[i] = s + 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 +} diff --git a/vendor/periph.io/x/periph/periph_parallel.go b/vendor/periph.io/x/periph/periph_parallel.go new file mode 100644 index 0000000..656e3f0 --- /dev/null +++ b/vendor/periph.io/x/periph/periph_parallel.go @@ -0,0 +1,101 @@ +// Copyright 2018 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. + +// This file contains the parallelized driver loading logic. It is meant to be +// load the drivers as fast as possible by parallelising work. + +// +build !tinygo + +package periph + +import ( + "errors" + "strconv" + "sync" +) + +func initImpl() (*State, error) { + state = &State{} + // At this point, byName is guaranteed to be immutable. + cD := make(chan Driver) + cS := make(chan DriverFailure) + cE := make(chan DriverFailure) + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + for d := range cD { + state.Loaded = insertDriver(state.Loaded, d) + } + }() + wg.Add(1) + go func() { + defer wg.Done() + for f := range cS { + state.Skipped = insertDriverFailure(state.Skipped, f) + } + }() + wg.Add(1) + go func() { + defer wg.Done() + for f := range cE { + state.Failed = insertDriverFailure(state.Failed, f) + } + }() + + stages, err := explodeStages() + if err != nil { + return state, err + } + loaded := make(map[string]struct{}, len(byName)) + for _, s := range stages { + s.loadParallel(loaded, cD, cS, cE) + } + close(cD) + close(cS) + close(cE) + wg.Wait() + return state, nil +} + +// loadParallel loads all the drivers for this stage in parallel. +// +// Updates loaded in a safe way. +func (s *stage) loadParallel(loaded map[string]struct{}, cD chan<- Driver, cS, cE chan<- DriverFailure) { + success := make(chan string) + go func() { + defer close(success) + wg := sync.WaitGroup{} + loop: + for name, drv := range s.drvs { + // Intentionally do not look at After(), only Prerequisites(). + for _, dep := range drv.Prerequisites() { + if _, ok := loaded[dep]; !ok { + cS <- DriverFailure{drv, errors.New("dependency not loaded: " + strconv.Quote(dep))} + continue loop + } + } + + // Not skipped driver, attempt loading in a goroutine. + wg.Add(1) + go func(n string, d Driver) { + defer wg.Done() + if ok, err := d.Init(); ok { + if err == nil { + cD <- d + success <- n + return + } + cE <- DriverFailure{d, err} + } else { + cS <- DriverFailure{d, err} + } + }(name, drv) + } + wg.Wait() + }() + for s := range success { + loaded[s] = struct{}{} + } +} diff --git a/vendor/periph.io/x/periph/periph_serial.go b/vendor/periph.io/x/periph/periph_serial.go new file mode 100644 index 0000000..3fa70c2 --- /dev/null +++ b/vendor/periph.io/x/periph/periph_serial.go @@ -0,0 +1,55 @@ +// Copyright 2018 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. + +// This file contains the single threaded driver loading code, to be used on +// low performance cores. + +// +build tinygo + +package periph + +import ( + "errors" + "strconv" +) + +func initImpl() (*State, error) { + state = &State{} + // At this point, byName is guaranteed to be immutable. + stages, err := explodeStages() + if err != nil { + return state, err + } + loaded := make(map[string]struct{}, len(byName)) + for _, s := range stages { + s.loadSerial(state, loaded) + } + return state, nil +} + +// loadSerial loads all the drivers for this stage, one after the other. +func (s *stage) loadSerial(state *State, loaded map[string]struct{}) { + for name, drv := range s.drvs { + // Intentionally do not look at After(), only Prerequisites(). + for _, dep := range drv.Prerequisites() { + if _, ok := loaded[dep]; !ok { + state.Skipped = insertDriverFailure(state.Skipped, DriverFailure{drv, errors.New("dependency not loaded: " + strconv.Quote(dep))}) + goto loop + } + } + + // Not skipped driver, attempt loading in a goroutine. + if ok, err := drv.Init(); ok { + if err == nil { + state.Loaded = insertDriver(state.Loaded, drv) + loaded[name] = struct{}{} + } else { + state.Failed = insertDriverFailure(state.Failed, DriverFailure{drv, err}) + } + } else { + state.Skipped = insertDriverFailure(state.Skipped, DriverFailure{drv, err}) + } + loop: + } +}