feat: initial commit

Supports:
- stealth mode
- fan control with hardware PWM

Signed-off-by: Matthias Riegler <matthias.riegler@ankorstore.com>
This commit is contained in:
Matthias Riegler
2023-07-03 08:44:09 +02:00
commit 933e44d1db
6 changed files with 267 additions and 0 deletions

1
README.md Normal file
View File

@@ -0,0 +1 @@
# computeblade-agent

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/xvzf/computeblade-agent
go 1.20

17
main.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "github.com/xvzf/computeblade-agent/pkg/hal"
func main() {
hal, err := hal.NewComputeBladeHAL(hal.ComputeBladeHalOpts{
ComputeModuleType: hal.COMPUTE_MODULE_TYPE_CM4,
FanUnit: hal.FAN_UNIT_STANDARD,
DefaultFanSpeed: 50,
DefaultStealthModeEnabled: true,
})
if err != nil {
panic(err)
}
defer hal.Close()
hal.Init()
}

40
pkg/hal/hal.go Normal file
View File

@@ -0,0 +1,40 @@
package hal
import "fmt"
type FanUnit uint8
type ComputeModule uint8
const (
FAN_UNIT_STANDARD = iota
FAN_UNIT_ADVANCED
)
const (
COMPUTE_MODULE_TYPE_CM4 ComputeModule = iota
)
type ComputeBladeHalOpts struct {
ComputeModuleType ComputeModule
FanUnit FanUnit
DefaultFanSpeed uint8
DefaultStealthModeEnabled bool
}
// 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)
}
// NewComputeBladeHAL returns a new HAL for the Compute Blade and a given configuration
func NewComputeBladeHAL(opts ComputeBladeHalOpts) (ComputeBladeHal, error) {
switch opts.ComputeModuleType {
case COMPUTE_MODULE_TYPE_CM4:
return NewBcm2711Hal(opts)
default:
return nil, fmt.Errorf("unsupported compute module type: %d", opts.ComputeModuleType)
}
}

179
pkg/hal/hal_bcm2711.go Normal file
View File

@@ -0,0 +1,179 @@
package hal
import (
"errors"
"os"
"sync"
"syscall"
"time"
)
const (
bcm2711PeripheryBaseAddr = 0xFE000000
bcm2711PwmAddr = bcm2711PeripheryBaseAddr + 0x20C000
bcm2711GpioAddr = bcm2711PeripheryBaseAddr + 0x200000
bcm2711ClkAddr = bcm2711PeripheryBaseAddr + 0x101000
bcm2711ClkManagerPwd = (0x5A << 24) //(31 - 24) on CM_GP0CTL/CM_GP1CTL/CM_GP2CTL regs
bcm2711FrontButtonPin = 20
bcm2711StealthPin = 21
bcm2711PwmFanPin = 12
bcm2711PwmTachPin = 13
)
type bcm2711hal struct {
// Config options
opts ComputeBladeHalOpts
wrMutex sync.Mutex
devmem *os.File
gpioMem8 []uint8
gpioMem []uint32
pwmMem8 []uint8
pwmMem []uint32
clkMem8 []uint8
clkMem []uint32
}
func NewBcm2711Hal(opts ComputeBladeHalOpts) (*bcm2711hal, 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 {
return nil, err
}
// Setup memory mappings
gpioMem, gpioMem8, err := mmap(devmem, bcm2711GpioAddr, 4096)
if err != nil {
return nil, err
}
pwmMem, pwmMem8, err := mmap(devmem, bcm2711PwmAddr, 4096)
if err != nil {
return nil, err
}
clkMem, clkMem8, err := mmap(devmem, bcm2711ClkAddr, 4096)
if err != nil {
return nil, err
}
return &bcm2711hal{
devmem: devmem,
gpioMem: gpioMem,
gpioMem8: gpioMem8,
pwmMem: pwmMem,
pwmMem8: pwmMem8,
clkMem: clkMem,
clkMem8: clkMem8,
opts: opts,
}, nil
}
// Close cleans all memory mappings
func (hal *bcm2711hal) Close() error {
return errors.Join(
syscall.Munmap(hal.gpioMem8),
syscall.Munmap(hal.pwmMem8),
syscall.Munmap(hal.clkMem8),
hal.devmem.Close(),
)
}
// Init initialises GPIOs and sets sane defaults
func (hal *bcm2711hal) Init() {
hal.InitGPIO()
hal.SetFanSpeed(hal.opts.DefaultFanSpeed)
hal.SetStealthMode(hal.opts.DefaultStealthModeEnabled)
}
// InitGPIO initalises GPIO configuration
func (hal *bcm2711hal) InitGPIO() {
// based on https://datasheets.raspberrypi.com/bcm2711/bcm2711-peripherals.pdf
hal.wrMutex.Lock()
defer hal.wrMutex.Unlock()
// Blade Butten (GPIO 20)
// -> GPFSEL2 2:0, input
hal.gpioMem[2] = (hal.gpioMem[2] &^ (0b111 << 0)) | (0b000 << 0)
// Stealth Mode Output (GPIO 21)
// -> GPFSEL2 5:3, output
hal.gpioMem[2] = (hal.gpioMem[2] &^ (0b111 << 3)) | (0b001 << 3)
// FAN PWM output for standard fan unit (GPIO 12)
if hal.opts.FanUnit == FAN_UNIT_STANDARD {
// -> GPFSEL1 8:6, alt0
hal.gpioMem[1] = (hal.gpioMem[1] &^ (0b111 << 6)) | (0b100 << 6)
// Stop pwm for both channels; this is required to set the new configuration
hal.pwmMem[0] &^= 1<<8 | 1
// Stop clock w/o any changes, they cannot be made in the same step
hal.clkMem[40] = bcm2711ClkManagerPwd | (hal.clkMem[40] &^ (1 << 4))
// Wait for the clock to not be busy so we can perform the changes
for hal.clkMem[40]&(1<<7) != 0 {
time.Sleep(time.Microsecond * 20)
}
// passwd, mash, disabled, source (oscillator)
hal.clkMem[40] = bcm2711ClkManagerPwd | (0 << 9) | (0 << 4) | (1 << 0)
// set PWM freq; the BCM2711 has an oscillator freq of 52 Mhz
// Noctua fans are expecting a 25khz signal, where duty cycle controls fan on/speed/off
// -> we'll need to get ~2.5Mhz of signal resultion in order to incorporate a 0-100 range
// The following settings setup ~2.571Mhz resultion, resulting in a ~25,71khz signal
// lying within the specifications of Noctua fans.
hal.clkMem[41] = bcm2711ClkManagerPwd | (20 << 12) | (3276 << 0)
// wait for changes to take effect before enabling it.
// Note: 10us seems sufficient on idle systems, but doesn't always work when
time.Sleep(time.Microsecond * 50)
// Start clock (passwd, mash, enable, source)
hal.clkMem[40] = bcm2711ClkManagerPwd | (0 << 9) | (1 << 4) | (1 << 0)
// Start pwm for both channels again
hal.pwmMem[0] &= 1<<8 | 1
}
// FAN TACH input for standard fan unit (GPIO 13)
if hal.opts.FanUnit == FAN_UNIT_STANDARD {
// -> GPFSEL1 11:9, input
hal.gpioMem[1] = (hal.gpioMem[1] &^ (0b111 << 9)) | (0b000 << 9)
}
// FIXME add pullup
// FIXME add WS2812 GPIO 18
}
// SetFanSpeed sets the fanspeed of a blade in percent (standard fan unit)
func (hal *bcm2711hal) SetFanSpeed(speed uint8) {
hal.setFanSpeedPWM(speed)
}
func (hal *bcm2711hal) setFanSpeedPWM(speed uint8) {
hal.wrMutex.Lock()
defer hal.wrMutex.Unlock()
// set MSEN=0
hal.pwmMem[0] = hal.pwmMem[0]&^(0xff) | (0 << 7) | (1 << 0)
hal.pwmMem[5] = uint32(speed)
hal.pwmMem[4] = 100
time.Sleep(3 * time.Microsecond)
}
func (hal *bcm2711hal) SetStealthMode(enable bool) {
hal.wrMutex.Lock()
defer hal.wrMutex.Unlock()
if enable {
// set high (bcm2711StealthPin == 21)
hal.gpioMem[7] = 1 << (bcm2711StealthPin)
} else {
// clear high state (bcm2711StealthPin == 21)
hal.gpioMem[10] = 1 << (bcm2711StealthPin)
}
}

27
pkg/hal/hal_common.go Normal file
View File

@@ -0,0 +1,27 @@
package hal
import (
"os"
"reflect"
"syscall"
"unsafe"
)
func mmap(file *os.File, base int64, lenght int) ([]uint32, []uint8, error) {
mem8, err := syscall.Mmap(
int(file.Fd()),
base,
lenght,
syscall.PROT_READ|syscall.PROT_WRITE,
syscall.MAP_SHARED,
)
if err != nil {
return nil, nil, err
}
// We'll have to work with 32 bit registers, so let's convert it.
header := *(*reflect.SliceHeader)(unsafe.Pointer(&mem8))
header.Len /= (32 / 8)
header.Cap /= (32 / 8)
mem32 := *(*[]uint32)(unsafe.Pointer(&header))
return mem32, mem8, nil
}