From dd49079918bb42db4a27b86ab4e0bf06d6ba3b76 Mon Sep 17 00:00:00 2001 From: Matthias Riegler Date: Mon, 17 Jul 2023 07:01:31 +0200 Subject: [PATCH] chore: refactore bcm2711 hardware abstraction layer Signed-off-by: Matthias Riegler --- pkg/computeblade/computeblade.go | 3 + pkg/computeblade/cover.cov | 20 ++ pkg/computeblade/hal/hal.go | 108 ++++++++ .../hal/hal_bcm2835.go} | 256 +++++++++++------- pkg/computeblade/hal/hal_mock.go | 50 ++++ .../bcm2711 => computeblade/hal}/helper.go | 3 +- pkg/hal/hal.go | 54 ---- 7 files changed, 335 insertions(+), 159 deletions(-) create mode 100644 pkg/computeblade/computeblade.go create mode 100644 pkg/computeblade/cover.cov create mode 100644 pkg/computeblade/hal/hal.go rename pkg/{hal/bcm2711/hal_bcm2711.go => computeblade/hal/hal_bcm2835.go} (50%) create mode 100644 pkg/computeblade/hal/hal_mock.go rename pkg/{hal/bcm2711 => computeblade/hal}/helper.go (94%) delete mode 100644 pkg/hal/hal.go diff --git a/pkg/computeblade/computeblade.go b/pkg/computeblade/computeblade.go new file mode 100644 index 0000000..510a3cd --- /dev/null +++ b/pkg/computeblade/computeblade.go @@ -0,0 +1,3 @@ +package computeblade + + diff --git a/pkg/computeblade/cover.cov b/pkg/computeblade/cover.cov new file mode 100644 index 0000000..0003509 --- /dev/null +++ b/pkg/computeblade/cover.cov @@ -0,0 +1,20 @@ +mode: set +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:40.56,46.2 1 1 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:49.84,62.2 1 1 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:65.89,74.2 1 0 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:86.54,94.2 1 1 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:96.64,97.30 1 1 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:97.30,99.3 1 0 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:101.2,105.12 4 1 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:109.56,111.6 1 1 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:111.6,113.69 1 1 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:113.69,115.4 1 0 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:117.2,118.44 1 1 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:118.44,119.11 1 1 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:121.21,122.22 1 1 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:124.22,126.21 2 1 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:128.32,129.43 1 0 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:130.5,131.19 2 0 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:131.19,133.6 1 0 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:134.5,134.57 1 0 +github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:134.57,136.6 1 0 diff --git a/pkg/computeblade/hal/hal.go b/pkg/computeblade/hal/hal.go new file mode 100644 index 0000000..b8930cb --- /dev/null +++ b/pkg/computeblade/hal/hal.go @@ -0,0 +1,108 @@ +package hal + +import ( + "context" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +type FanUnit uint8 +type ComputeModule uint8 +type PowerStatus uint8 + +var ( + fanSpeedTargetPercent = promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: "computeblade", + Name: "fan_speed_target_percent", + Help: "Target fanspeed in percent", + }) + fanSpeed = promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: "computeblade", + Name: "fan_speed", + Help: "Fan speed in RPM", + }) + computeModule = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "computeblade", + Name: "compute_modul_present", + Help: "Compute module type", + }, []string{"type"}) + ledColorChangeEventCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: "computeblade", + Name: "led_color_change_event_count", + Help: "Led color change event_count", + }) + powerStatus = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "computeblade", + Name: "power_status", + Help: "Power status of the blade", + }, []string{"type"}) + stealthModeEnabled = promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: "computeblade", + Name: "stealth_mode_enabled", + Help: "Stealth mode enabled", + }) + fanUnit = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "computeblade", + Name: "fan_unit", + Help: "Fan unit", + }, []string{"type"}) + edgeButtonEventCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: "computeblade", + Name: "edge_button_event_count", + Help: "Number of edge button presses", + }) +) + +func (p PowerStatus) String() string { + switch p { + case PowerPoe802at: + return "poe+" + case PowerPoeOrUsbC: + return "poeOrUsbC" + default: + return "undefined" + } +} + +const ( + FanUnitStandard = iota + FanUnitSmart +) + +const ( + PowerPoeOrUsbC = iota + PowerPoe802at +) + +const ( + LedTop = iota + LedEdge +) + +type LedColor struct { + Red uint8 + Green uint8 + Blue uint8 +} + +type ComputeBladeHalOpts struct { + FanUnit FanUnit +} + +// ComputeBladeHal abstracts hardware details of the Compute Blade and provides a simple interface +type ComputeBladeHal interface { + Close() error + // SetFanSpeed sets the fan speed in percent + SetFanSpeed(speed uint8) error + // GetFanSpeed returns the current fan speed in percent (based on moving average) + GetFanRPM() (float64, error) + // SetStealthMode enables/disables stealth mode of the blade (turning on/off the LEDs) + SetStealthMode(enabled bool) error + // SetLEDs sets the color of the LEDs + SetLed(idx uint, color LedColor) error + // GetPowerStatus returns the current power status of the blade + GetPowerStatus() (PowerStatus, error) + // GetEdgeButtonPressChan returns a channel emitting edge button press events + WaitForEdgeButtonPress(ctx context.Context) error +} diff --git a/pkg/hal/bcm2711/hal_bcm2711.go b/pkg/computeblade/hal/hal_bcm2835.go similarity index 50% rename from pkg/hal/bcm2711/hal_bcm2711.go rename to pkg/computeblade/hal/hal_bcm2835.go index a434691..58cd16b 100644 --- a/pkg/hal/bcm2711/hal_bcm2711.go +++ b/pkg/computeblade/hal/hal_bcm2835.go @@ -1,6 +1,8 @@ -package bcm2711 +//go:build linux +package hal import ( + "context" "errors" "fmt" "os" @@ -10,44 +12,43 @@ import ( "github.com/warthog618/gpiod" "github.com/warthog618/gpiod/device/rpi" - "github.com/xvzf/computeblade-agent/pkg/hal" ) const ( - bcm2711PeripheryBaseAddr = 0xFE000000 - bcm2711RegPwmAddr = bcm2711PeripheryBaseAddr + 0x20C000 - bcm2711GpioAddr = bcm2711PeripheryBaseAddr + 0x200000 - bcm2711ClkAddr = bcm2711PeripheryBaseAddr + 0x101000 - bcm2711ClkManagerPwd = (0x5A << 24) //(31 - 24) on CM_GP0CTL/CM_GP1CTL/CM_GP2CTL regs - bcm2711PageSize = 4096 // theoretical page size + bcm283xPeripheryBaseAddr = 0xFE000000 + bcm283xRegPwmAddr = bcm283xPeripheryBaseAddr + 0x20C000 + bcm283xGpioAddr = bcm283xPeripheryBaseAddr + 0x200000 + bcm283xClkAddr = bcm283xPeripheryBaseAddr + 0x101000 + bcm283xClkManagerPwd = (0x5A << 24) //(31 - 24) on CM_GP0CTL/CM_GP1CTL/CM_GP2CTL regs + bcm283xPageSize = 4096 // theoretical page size - bcm2711FrontButtonPin = 20 - bcm2711StealthPin = 21 - bcm2711RegPwmTachPin = 13 + bcm283xFrontButtonPin = 20 + bcm283xStealthPin = 21 + bcm283xRegPwmTachPin = 13 - bcm2711RegGpfsel1 = 0x01 + bcm283xRegGpfsel1 = 0x01 - bcm2711RegPwmCtl = 0x00 - bcm2711RegPwmRng1 = 0x04 - bcm2711RegPwmFif1 = 0x06 + bcm283xRegPwmCtl = 0x00 + bcm283xRegPwmRng1 = 0x04 + bcm283xRegPwmFif1 = 0x06 - bcm2711RegPwmCtlBitPwen2 = 8 // Enable (pwm2) - bcm2711RegPwmCtlBitClrf1 = 6 // Clear FIFO - bcm2711RegPwmCtlBitUsef1 = 5 // Use FIFO - bcm2711RegPwmCtlBitSbit1 = 3 // Line level when not transmitting - bcm2711RegPwmCtlBitRptl1 = 2 // Repeat last data when FIFO is empty - bcm2711RegPwmCtlBitMode1 = 1 // Mode; 0: PWM, 1: Serializer - bcm2711RegPwmCtlBitPwen1 = 0 // Enable (pwm1) + bcm283xRegPwmCtlBitPwen2 = 8 // Enable (pwm2) + bcm283xRegPwmCtlBitClrf1 = 6 // Clear FIFO + bcm283xRegPwmCtlBitUsef1 = 5 // Use FIFO + bcm283xRegPwmCtlBitSbit1 = 3 // Line level when not transmitting + bcm283xRegPwmCtlBitRptl1 = 2 // Repeat last data when FIFO is empty + bcm283xRegPwmCtlBitMode1 = 1 // Mode; 0: PWM, 1: Serializer + bcm283xRegPwmCtlBitPwen1 = 0 // Enable (pwm1) - bcm2711RegPwmclkCntrl = 0x28 - bcm2711RegPwmclkDiv = 0x29 - bcm2711RegPwmclkCntrlBitSrcOsc = 0 - bcm2711RegPwmclkCntrlBitEnable = 4 + bcm283xRegPwmclkCntrl = 0x28 + bcm283xRegPwmclkDiv = 0x29 + bcm283xRegPwmclkCntrlBitSrcOsc = 0 + bcm283xRegPwmclkCntrlBitEnable = 4 ) -type bcm2711 struct { +type bcm283x struct { // Config options - opts hal.ComputeBladeHalOpts + opts ComputeBladeHalOpts wrMutex sync.Mutex @@ -63,11 +64,15 @@ type bcm2711 struct { clkMem []uint32 gpioChip0 *gpiod.Chip + // Save LED colors so the pixels can be updated individually + leds [2]LedColor + // Stealth mode output stealthModeLine *gpiod.Line // Edge button input - edgeButtonLine *gpiod.Line + edgeButtonLine *gpiod.Line + edgeButtonWatchChan chan struct{} // PoE detection input poeLine *gpiod.Line @@ -78,7 +83,7 @@ type bcm2711 struct { fanRpm float64 } -func New(opts hal.ComputeBladeHalOpts) (*bcm2711, error) { +func NewCm4Hal(opts ComputeBladeHalOpts) (ComputeBladeHal, error) { // /dev/gpiomem doesn't allow complex operations for PWM fan control or WS281x devmem, err := os.OpenFile("/dev/mem", os.O_RDWR|os.O_SYNC, os.ModePerm) if err != nil { @@ -91,36 +96,39 @@ func New(opts hal.ComputeBladeHalOpts) (*bcm2711, error) { } // Setup memory mappings - gpioMem, gpioMem8, err := mmap(devmem, bcm2711GpioAddr, bcm2711PageSize) + gpioMem, gpioMem8, err := mmap(devmem, bcm283xGpioAddr, bcm283xPageSize) if err != nil { return nil, err } - pwmMem, pwmMem8, err := mmap(devmem, bcm2711RegPwmAddr, bcm2711PageSize) + pwmMem, pwmMem8, err := mmap(devmem, bcm283xRegPwmAddr, bcm283xPageSize) if err != nil { return nil, err } - clkMem, clkMem8, err := mmap(devmem, bcm2711ClkAddr, bcm2711PageSize) + clkMem, clkMem8, err := mmap(devmem, bcm283xClkAddr, bcm283xPageSize) if err != nil { return nil, err } - bcm := &bcm2711{ - devmem: devmem, - gpioMem: gpioMem, - gpioMem8: gpioMem8, - pwmMem: pwmMem, - pwmMem8: pwmMem8, - clkMem: clkMem, - clkMem8: clkMem8, - gpioChip0: gpioChip0, - opts: opts, + bcm := &bcm283x{ + devmem: devmem, + gpioMem: gpioMem, + gpioMem8: gpioMem8, + pwmMem: pwmMem, + pwmMem8: pwmMem8, + clkMem: clkMem, + clkMem8: clkMem8, + gpioChip0: gpioChip0, + opts: opts, + edgeButtonWatchChan: make(chan struct{}), } + computeModule.WithLabelValues("cm4").Set(1) + return bcm, bcm.setup() } // Close cleans all memory mappings -func (bcm *bcm2711) Close() error { +func (bcm *bcm283x) Close() error { errs := errors.Join( syscall.Munmap(bcm.gpioMem8), syscall.Munmap(bcm.pwmMem8), @@ -140,7 +148,7 @@ func (bcm *bcm2711) Close() error { // handleFanEdge handles an edge event on the fan tach input for the standard fan unite. // Exponential moving average is used to smooth out the fan speed. -func (bcm *bcm2711) handleFanEdge(evt gpiod.LineEvent) { +func (bcm *bcm283x) handleFanEdge(evt gpiod.LineEvent) { // Ensure we're always storing the last event defer func() { bcm.lastFanEdgeEvent = &evt @@ -158,14 +166,28 @@ func (bcm *bcm2711) handleFanEdge(evt gpiod.LineEvent) { // Simple moving average to smooth out the fan speed bcm.fanRpm = (rpm * 0.1) + (bcm.fanRpm * 0.9) + fanSpeed.Set(bcm.fanRpm) } -func (bcm *bcm2711) handleEdgeButtonEdge(evt gpiod.LineEvent) { - fmt.Printf("button pressed\n") +func (bcm *bcm283x) handleEdgeButtonEdge(evt gpiod.LineEvent) { + edgeButtonEventCount.Inc() + close(bcm.edgeButtonWatchChan) + bcm.edgeButtonWatchChan = make(chan struct{}) +} + +// GetEdgeButtonPressChan returns a channel that can be used to watch for edge button presses +func (bcm *bcm283x) GetEdgeButtonPressChan(ctx context.Context) error { + // Either wait for the context to be cancelled or the edge button to be pressed + select { + case <-ctx.Done(): + return ctx.Err() + case <-bcm.edgeButtonWatchChan: + return nil + } } // Init initialises GPIOs and sets sane defaults -func (bcm *bcm2711) setup() error { +func (bcm *bcm283x) setup() error { var err error = nil // Register edge event handler for edge button @@ -177,7 +199,7 @@ func (bcm *bcm2711) setup() error { } // Register input for PoE detection - bcm.poeLine, err = bcm.gpioChip0.RequestLine(rpi.GPIO23, gpiod.AsInput) + bcm.poeLine, err = bcm.gpioChip0.RequestLine(rpi.GPIO23, gpiod.AsInput, gpiod.WithPullUp) if err != nil { return err } @@ -189,40 +211,48 @@ func (bcm *bcm2711) setup() error { } // standard fan unit - if bcm.opts.FanUnit == hal.FanUnitStandard { + if bcm.opts.FanUnit == FanUnitStandard { + fanUnit.WithLabelValues("standard").Set(1) // FAN PWM output for standard fan unit (GPIO 12) - // -> bcm2711RegGpfsel1 8:6, alt0 - bcm.gpioMem[bcm2711RegGpfsel1] = (bcm.gpioMem[bcm2711RegGpfsel1] &^ (0b111 << 6)) | (0b100 << 6) + // -> bcm283xRegGpfsel1 8:6, alt0 + bcm.gpioMem[bcm283xRegGpfsel1] = (bcm.gpioMem[bcm283xRegGpfsel1] &^ (0b111 << 6)) | (0b100 << 6) // Register edge event handler for fan tach input - bcm.fanEdgeLine, err = bcm.gpioChip0.RequestLine(rpi.GPIO13, gpiod.WithEventHandler(bcm.handleFanEdge), gpiod.WithFallingEdge, gpiod.WithPullUp) + bcm.fanEdgeLine, err = bcm.gpioChip0.RequestLine( + rpi.GPIO13, + gpiod.WithEventHandler(bcm.handleFanEdge), + gpiod.WithFallingEdge, + gpiod.WithPullUp, + ) if err != nil { return err } } - // Setup defaults - - bcm.SetFanSpeed(bcm.opts.Defaults.FanSpeed) - bcm.SetStealthMode(bcm.opts.Defaults.StealthModeEnabled) - bcm.SetLEDs(bcm.opts.Defaults.TopLedColor, bcm.opts.Defaults.EdgeLedColor) - return err } -func (bcm2711 *bcm2711) GetFanSpeed() int { - return int(bcm2711.fanRpm) +func (bcm283x *bcm283x) GetFanRPM() (float64, error) { + return bcm283x.fanRpm, nil } -func (bcm *bcm2711) GetPowerStatus() hal.PowerStatus { - +func (bcm *bcm283x) GetPowerStatus() (PowerStatus, error) { // GPIO 23 is used for PoE detection - if val, err := bcm.poeLine.Value(); err != nil && val == 1 { - return hal.PowerPoe802at + val, err := bcm.poeLine.Value() + if err != nil { + return PowerPoeOrUsbC, err } - return hal.PowerPoeOrUsbC + + if val > 0 { + powerStatus.WithLabelValues(fmt.Sprint(PowerPoe802at)).Set(1) + powerStatus.WithLabelValues(fmt.Sprint(PowerPoeOrUsbC)).Set(0) + return PowerPoe802at, nil + } + powerStatus.WithLabelValues(fmt.Sprint(PowerPoe802at)).Set(0) + powerStatus.WithLabelValues(fmt.Sprint(PowerPoeOrUsbC)).Set(1) + return PowerPoeOrUsbC, nil } -func (bcm *bcm2711) setPwm0Freq(targetFrequency uint64) error { +func (bcm *bcm283x) setPwm0Freq(targetFrequency uint64) error { // Calculate PWM divisor based on target frequency divisor := 54000000 / targetFrequency realDivisor := divisor & 0xfff // 12 bits @@ -231,42 +261,44 @@ func (bcm *bcm2711) setPwm0Freq(targetFrequency uint64) error { } // Stop pwm for both channels; this is required to set the new configuration - bcm.pwmMem[bcm2711RegPwmCtl] &^= (1 << bcm2711RegPwmCtlBitPwen1) | (1 << bcm2711RegPwmCtlBitPwen2) + bcm.pwmMem[bcm283xRegPwmCtl] &^= (1 << bcm283xRegPwmCtlBitPwen1) | (1 << bcm283xRegPwmCtlBitPwen2) time.Sleep(time.Microsecond * 10) // Stop clock w/o any changes, they cannot be made in the same step - bcm.clkMem[bcm2711RegPwmclkCntrl] = bcm2711ClkManagerPwd | (bcm.clkMem[bcm2711RegPwmclkCntrl] &^ (1 << 4)) + bcm.clkMem[bcm283xRegPwmclkCntrl] = bcm283xClkManagerPwd | (bcm.clkMem[bcm283xRegPwmclkCntrl] &^ (1 << 4)) time.Sleep(time.Microsecond * 10) // Wait for the clock to not be busy so we can perform the changes - for bcm.clkMem[bcm2711RegPwmclkCntrl]&(1<<7) != 0 { + for bcm.clkMem[bcm283xRegPwmclkCntrl]&(1<<7) != 0 { time.Sleep(time.Microsecond * 10) } // passwd, disabled, source (oscillator) - bcm.clkMem[bcm2711RegPwmclkCntrl] = bcm2711ClkManagerPwd | (0 << bcm2711RegPwmclkCntrlBitEnable) | (1 << bcm2711RegPwmclkCntrlBitSrcOsc) + bcm.clkMem[bcm283xRegPwmclkCntrl] = bcm283xClkManagerPwd | (0 << bcm283xRegPwmclkCntrlBitEnable) | (1 << bcm283xRegPwmclkCntrlBitSrcOsc) time.Sleep(time.Microsecond * 10) - bcm.clkMem[bcm2711RegPwmclkDiv] = bcm2711ClkManagerPwd | (uint32(divisor) << 12) + bcm.clkMem[bcm283xRegPwmclkDiv] = bcm283xClkManagerPwd | (uint32(divisor) << 12) time.Sleep(time.Microsecond * 10) // Start clock (passwd, enable, source) - bcm.clkMem[bcm2711RegPwmclkCntrl] = bcm2711ClkManagerPwd | (1 << bcm2711RegPwmclkCntrlBitEnable) | (1 << bcm2711RegPwmclkCntrlBitSrcOsc) + bcm.clkMem[bcm283xRegPwmclkCntrl] = bcm283xClkManagerPwd | (1 << bcm283xRegPwmclkCntrlBitEnable) | (1 << bcm283xRegPwmclkCntrlBitSrcOsc) time.Sleep(time.Microsecond * 10) // Start pwm for both channels again - bcm.pwmMem[bcm2711RegPwmCtl] &= (1 << bcm2711RegPwmCtlBitPwen1) + bcm.pwmMem[bcm283xRegPwmCtl] &= (1 << bcm283xRegPwmCtlBitPwen1) time.Sleep(time.Microsecond * 10) return nil } // SetFanSpeed sets the fanspeed of a blade in percent (standard fan unit) -func (bcm *bcm2711) SetFanSpeed(speed uint8) { +func (bcm *bcm283x) SetFanSpeed(speed uint8) error { + fanSpeedTargetPercent.Set(float64(speed)) bcm.setFanSpeedPWM(speed) + return nil } -func (bcm *bcm2711) setFanSpeedPWM(speed uint8) { +func (bcm *bcm283x) setFanSpeedPWM(speed uint8) { // Noctua fans are expecting a 25khz signal, where duty cycle controls fan on/speed/off // With the usage of the FIFO, we can alter the duty cycle by the number of bits set in the FIFO, maximum of 32. // We therefore need a frequency of 32*25khz = 800khz, which is a divisor of 67.5 (thus we'll use 68). @@ -290,21 +322,23 @@ func (bcm *bcm2711) setFanSpeedPWM(speed uint8) { } // Use fifo, repeat, ... - bcm.pwmMem[bcm2711RegPwmCtl] = (1 << bcm2711RegPwmCtlBitPwen1) | (1 << bcm2711RegPwmCtlBitMode1) | (1 << bcm2711RegPwmCtlBitRptl1) | (1 << bcm2711RegPwmCtlBitUsef1) + bcm.pwmMem[bcm283xRegPwmCtl] = (1 << bcm283xRegPwmCtlBitPwen1) | (1 << bcm283xRegPwmCtlBitMode1) | (1 << bcm283xRegPwmCtlBitRptl1) | (1 << bcm283xRegPwmCtlBitUsef1) time.Sleep(10 * time.Microsecond) - bcm.pwmMem[bcm2711RegPwmRng1] = 32 + bcm.pwmMem[bcm283xRegPwmRng1] = 32 time.Sleep(10 * time.Microsecond) - bcm.pwmMem[bcm2711RegPwmFif1] = targetvalue + bcm.pwmMem[bcm283xRegPwmFif1] = targetvalue // Store fan speed for later use bcm.currFanSpeed = speed } -func (bcm *bcm2711) SetStealthMode(enable bool) { +func (bcm *bcm283x) SetStealthMode(enable bool) error { if enable { - _ = bcm.stealthModeLine.SetValue(1) + stealthModeEnabled.Set(1) + return bcm.stealthModeLine.SetValue(1) } else { - _ = bcm.stealthModeLine.SetValue(0) + stealthModeEnabled.Set(0) + return bcm.stealthModeLine.SetValue(0) } } @@ -324,53 +358,67 @@ func serializePwmDataFrame(data uint8) uint32 { return result } -// SetLEDs sets the color of the WS281x LEDs -func (bcm *bcm2711) SetLEDs(top hal.LedColor, edge hal.LedColor) { +func (bcm *bcm283x) SetLed(idx uint, color LedColor) error { + if idx >= 2 { + return fmt.Errorf("invalid led index %d, supported: [0, 1]", idx) + } + + bcm.leds[idx] = color + + return bcm.updateLEDs() +} + +// updateLEDs sets the color of the WS281x LEDs +func (bcm *bcm283x) updateLEDs() error { bcm.wrMutex.Lock() defer bcm.wrMutex.Unlock() + ledColorChangeEventCount.Inc() + // Set frequency to 3*800khz. // we'll bit-bang the data, so we'll need to send 3 bits per bit of data. bcm.setPwm0Freq(3 * 800000) time.Sleep(10 * time.Microsecond) // WS281x Output (GPIO 18) - // -> bcm2711RegGpfsel1 24:26, regular output; it's configured as alt5 whenever pixel data is sent. + // -> bcm283xRegGpfsel1 24:26, regular output; it's configured as alt5 whenever pixel data is sent. // This is not optimal but required as the pwm0 peripheral is shared between fan and data line for the LEDs. time.Sleep(10 * time.Microsecond) - bcm.gpioMem[bcm2711RegGpfsel1] = (bcm.gpioMem[bcm2711RegGpfsel1] &^ (0b111 << 24)) | (0b010 << 24) + bcm.gpioMem[bcm283xRegGpfsel1] = (bcm.gpioMem[bcm283xRegGpfsel1] &^ (0b111 << 24)) | (0b010 << 24) time.Sleep(10 * time.Microsecond) defer func() { // Set to regular output again so the PWM signal doesn't confuse the WS2812 - bcm.gpioMem[bcm2711RegGpfsel1] = (bcm.gpioMem[bcm2711RegGpfsel1] &^ (0b111 << 24)) | (0b001 << 24) + bcm.gpioMem[bcm283xRegGpfsel1] = (bcm.gpioMem[bcm283xRegGpfsel1] &^ (0b111 << 24)) | (0b001 << 24) bcm.setFanSpeedPWM(bcm.currFanSpeed) }() - bcm.pwmMem[bcm2711RegPwmCtl] = (1 << bcm2711RegPwmCtlBitMode1) | (1 << bcm2711RegPwmCtlBitRptl1) | (0 << bcm2711RegPwmCtlBitSbit1) | (1 << bcm2711RegPwmCtlBitUsef1) | (1 << bcm2711RegPwmCtlBitClrf1) + bcm.pwmMem[bcm283xRegPwmCtl] = (1 << bcm283xRegPwmCtlBitMode1) | (1 << bcm283xRegPwmCtlBitRptl1) | (0 << bcm283xRegPwmCtlBitSbit1) | (1 << bcm283xRegPwmCtlBitUsef1) | (1 << bcm283xRegPwmCtlBitClrf1) time.Sleep(10 * time.Microsecond) - // bcm.pwmMem[bcm2711RegPwmRng1] = 32 - bcm.pwmMem[bcm2711RegPwmRng1] = 24 // we only need 24 bits per LED + // bcm.pwmMem[bcm283xRegPwmRng1] = 32 + bcm.pwmMem[bcm283xRegPwmRng1] = 24 // we only need 24 bits per LED time.Sleep(10 * time.Microsecond) // Add sufficient padding to clear 50us of silence with ~412.5ns per bit -> at least 121 bits -> let's be safe and send 6*24=144 bits of silence - bcm.pwmMem[bcm2711RegPwmFif1] = 0 - bcm.pwmMem[bcm2711RegPwmFif1] = 0 - bcm.pwmMem[bcm2711RegPwmFif1] = 0 - bcm.pwmMem[bcm2711RegPwmFif1] = 0 - bcm.pwmMem[bcm2711RegPwmFif1] = 0 - bcm.pwmMem[bcm2711RegPwmFif1] = 0 + bcm.pwmMem[bcm283xRegPwmFif1] = 0 + bcm.pwmMem[bcm283xRegPwmFif1] = 0 + bcm.pwmMem[bcm283xRegPwmFif1] = 0 + bcm.pwmMem[bcm283xRegPwmFif1] = 0 + bcm.pwmMem[bcm283xRegPwmFif1] = 0 + bcm.pwmMem[bcm283xRegPwmFif1] = 0 // Write top LED data - bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(top.Red) << 8 - bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(top.Green) << 8 - bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(top.Blue) << 8 + bcm.pwmMem[bcm283xRegPwmFif1] = serializePwmDataFrame(bcm.leds[0].Red) << 8 + bcm.pwmMem[bcm283xRegPwmFif1] = serializePwmDataFrame(bcm.leds[0].Green) << 8 + bcm.pwmMem[bcm283xRegPwmFif1] = serializePwmDataFrame(bcm.leds[0].Blue) << 8 // Write edge LED data - bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(edge.Red) << 8 - bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(edge.Green) << 8 - bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(edge.Blue) << 8 + bcm.pwmMem[bcm283xRegPwmFif1] = serializePwmDataFrame(bcm.leds[1].Red) << 8 + bcm.pwmMem[bcm283xRegPwmFif1] = serializePwmDataFrame(bcm.leds[1].Green) << 8 + bcm.pwmMem[bcm283xRegPwmFif1] = serializePwmDataFrame(bcm.leds[1].Blue) << 8 // make sure there's >50us of silence - bcm.pwmMem[bcm2711RegPwmFif1] = 0 // auto-repeated, so no need to feed the FIFO further. + bcm.pwmMem[bcm283xRegPwmFif1] = 0 // auto-repeated, so no need to feed the FIFO further. - bcm.pwmMem[bcm2711RegPwmCtl] = (1 << bcm2711RegPwmCtlBitPwen1) | (1 << bcm2711RegPwmCtlBitMode1) | (1 << bcm2711RegPwmCtlBitRptl1) | (0 << bcm2711RegPwmCtlBitSbit1) | (1 << bcm2711RegPwmCtlBitUsef1) + bcm.pwmMem[bcm283xRegPwmCtl] = (1 << bcm283xRegPwmCtlBitPwen1) | (1 << bcm283xRegPwmCtlBitMode1) | (1 << bcm283xRegPwmCtlBitRptl1) | (0 << bcm283xRegPwmCtlBitSbit1) | (1 << bcm283xRegPwmCtlBitUsef1) // sleep for 4*50us to ensure the data is sent. This is probably a bit too gracious but does not have a significant impact, so let's be safe data gets out. time.Sleep(200 * time.Microsecond) + + return nil } diff --git a/pkg/computeblade/hal/hal_mock.go b/pkg/computeblade/hal/hal_mock.go new file mode 100644 index 0000000..6401d82 --- /dev/null +++ b/pkg/computeblade/hal/hal_mock.go @@ -0,0 +1,50 @@ +package hal + +import ( + "context" + + "github.com/stretchr/testify/mock" +) + +// fails if ComputeBladeHalMock does not implement ComputeBladeHal +var _ ComputeBladeHal = &ComputeBladeHalMock{} + +// ComputeBladeMock implements a mock for the ComputeBladeHal interface +type ComputeBladeHalMock struct { + mock.Mock +} + +func (m *ComputeBladeHalMock) Close() error { + args := m.Called() + return args.Error(0) +} + +func (m *ComputeBladeHalMock) SetFanSpeed(percent uint8) error { + args := m.Called(percent) + return args.Error(0) +} + +func (m *ComputeBladeHalMock) GetFanRPM() (float64, error) { + args := m.Called() + return args.Get(0).(float64), args.Error(1) +} + +func (m *ComputeBladeHalMock) SetStealthMode(enabled bool) error { + args := m.Called(enabled) + return args.Error(0) +} + +func (m *ComputeBladeHalMock) GetPowerStatus() (PowerStatus, error) { + args := m.Called() + return args.Get(0).(PowerStatus), args.Error(1) +} + +func (m *ComputeBladeHalMock) WaitForEdgeButtonPress(ctx context.Context) error { + args := m.Called(ctx) + return args.Error(0) +} + +func (m *ComputeBladeHalMock) SetLed(idx uint, color LedColor) error { + args := m.Called(idx, color) + return args.Error(0) +} diff --git a/pkg/hal/bcm2711/helper.go b/pkg/computeblade/hal/helper.go similarity index 94% rename from pkg/hal/bcm2711/helper.go rename to pkg/computeblade/hal/helper.go index 6818c0b..d0b2473 100644 --- a/pkg/hal/bcm2711/helper.go +++ b/pkg/computeblade/hal/helper.go @@ -1,4 +1,5 @@ -package bcm2711 +//go:build linux +package hal import ( "os" diff --git a/pkg/hal/hal.go b/pkg/hal/hal.go deleted file mode 100644 index adbb22d..0000000 --- a/pkg/hal/hal.go +++ /dev/null @@ -1,54 +0,0 @@ -package hal - -type FanUnit uint8 -type ComputeModule uint8 -type PowerStatus uint8 - -func (p PowerStatus) String() string { - switch p { - case PowerPoe802at: - return "poe802at" - case PowerPoeOrUsbC: - return "poeOrUsbC" - default: - return "undefined" - } -} - -const ( - FanUnitStandard = iota - FanUnitSmart -) - -const ( - PowerPoeOrUsbC = iota - PowerPoe802at -) - -type LedColor struct { - Red uint8 - Green uint8 - Blue uint8 -} - -type ComputeBladeHalOptsDefault struct { - StealthModeEnabled bool - FanSpeed uint8 - TopLedColor LedColor - EdgeLedColor LedColor -} - -type ComputeBladeHalOpts struct { - FanUnit FanUnit - Defaults ComputeBladeHalOptsDefault -} - -// COmputeBladeHal abstracts hardware details of the Compute Blade and provides a simple interface -type ComputeBladeHal interface { - Init() - Close() error - SetFanSpeed(speed uint8) - SetStealthMode(enabled bool) - GetPowerStatus() PowerStatus - SetLEDs(top, edge LedColor) -}