initial setup of an app to toggle acpi wakeup devices

main
Nikita Tokarchuk 4 months ago
parent b7c9a19f6a
commit 3edb64f1ed
Signed by: mainnika
GPG Key ID: 5CDEED14F48FA79D
  1. 74
      cmd/acpi-wakeup-fixxer/main.go
  2. 16
      go.mod
  3. 18
      go.sum
  4. 36
      pkg/procfs/procfs.go
  5. 88
      pkg/wakeup/wakeup.go
  6. 105
      pkg/wakeup/wakeup_test.go

@ -0,0 +1,74 @@
package main
import (
"fmt"
"log/slog"
"os"
"github.com/spf13/cobra"
"code.tokarch.uk/mainnika/acpi-wakeup-fixxer/pkg/wakeup"
)
var (
devicesToDisableFlag []string
)
var rootCmd = &cobra.Command{
Use: "acpi-wakeup-fixxer",
Short: "A CLI tool to fix ACPI wakeup issues",
Args: cobra.NoArgs,
Long: "acpi-wakeup-fixxer is a command-line tool to fix ACPI wakeup issues on your system.",
Run: rootCmdRun,
}
func init() {
rootCmd.PersistentFlags().StringArrayVar(&devicesToDisableFlag, "disable", []string{"LID0", "XHC1"}, "Devices to disable")
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func rootCmdRun(cmd *cobra.Command, args []string) {
wakeUpController := wakeup.NewWakeupController()
devices, err := wakeUpController.GetWakeupDevices(wakeup.StatusEnabled)
if err != nil {
slog.Error("failed to get wakeup devices", "error", err)
os.Exit(1)
}
slog.Info("Found devices", "devices", devices)
slog.Info("Disabling devices", "devices", devicesToDisableFlag)
var disabledDevices []string
for _, device := range devices {
needDisable := false
for _, deviceToDisable := range devicesToDisableFlag {
if device == deviceToDisable {
needDisable = true
break
}
}
if needDisable {
slog.Info("Disabling device", "device", device)
if err := wakeUpController.ToggleWakeupDevice(device); err != nil {
slog.Error("failed to disable device", "device", device, "error", err)
continue
}
disabledDevices = append(disabledDevices, device)
}
}
if len(disabledDevices) > 0 {
slog.Info("Disabled devices", "devices", disabledDevices)
} else {
slog.Info("No devices were disabled")
}
}

@ -0,0 +1,16 @@
module code.tokarch.uk/mainnika/acpi-wakeup-fixxer
go 1.22.2
require (
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.9.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

@ -0,0 +1,18 @@
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -0,0 +1,36 @@
package procfs
import (
"fmt"
"io"
"os"
)
const (
acpiWakeupDefaultPath = "/proc/acpi/wakeup"
)
type Procfs interface {
ACPIWakeup() (io.ReadCloser, error)
ACPIWakeupWrite() (io.WriteCloser, error)
}
type ProcfsDefaultPath struct{}
func (p *ProcfsDefaultPath) ACPIWakeup() (io.ReadCloser, error) {
f, err := os.Open(acpiWakeupDefaultPath)
if err != nil {
return nil, fmt.Errorf("failed to open %s: %w", acpiWakeupDefaultPath, err)
}
return f, nil
}
func (p *ProcfsDefaultPath) ACPIWakeupWrite() (io.WriteCloser, error) {
f, err := os.OpenFile(acpiWakeupDefaultPath, os.O_WRONLY, 0)
if err != nil {
return nil, fmt.Errorf("failed to open for writing %s: %w", acpiWakeupDefaultPath, err)
}
return f, nil
}

@ -0,0 +1,88 @@
package wakeup
import (
"bufio"
"fmt"
"log/slog"
"strings"
"code.tokarch.uk/mainnika/acpi-wakeup-fixxer/pkg/procfs"
)
const (
deviceHeader = "Device"
columnDeviceIndex = 0
columnStatusIndex = 2
columnIndexMax = 3
)
type Status string
const StatusAll Status = ""
const StatusEnabled Status = "*enabled"
const StatusDisabled Status = "*disabled"
type WakeupController struct {
ProcfsProvider procfs.Procfs
}
func NewWakeupController() *WakeupController {
return &WakeupController{ProcfsProvider: &procfs.ProcfsDefaultPath{}}
}
func (w *WakeupController) GetWakeupDevices(withStatus Status) ([]string, error) {
wakeupFile, err := w.ProcfsProvider.ACPIWakeup()
if err != nil {
return nil, fmt.Errorf("failed to get wakeup file: %w", err)
}
defer func() { _ = wakeupFile.Close() }()
scanner := bufio.NewScanner(wakeupFile)
var devices []string
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) < columnIndexMax {
slog.Warn("unexpected number of columns", "line", line)
continue
}
device := fields[columnDeviceIndex]
if device == deviceHeader {
continue
}
status := Status(fields[columnStatusIndex])
if withStatus != StatusAll && status != withStatus {
continue
}
devices = append(devices, device)
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("failed to scan wakeup file: %w", err)
}
return devices, nil
}
func (w *WakeupController) ToggleWakeupDevice(device string) error {
wakeupFile, err := w.ProcfsProvider.ACPIWakeupWrite()
if err != nil {
return fmt.Errorf("failed to open wakeup file for writing: %w", err)
}
_, err = fmt.Fprintf(wakeupFile, "%s\n", device)
if err != nil {
_ = wakeupFile.Close()
return fmt.Errorf("failed to write to wakeup file: %w", err)
}
err = wakeupFile.Close()
if err != nil {
return fmt.Errorf("failed to close wakeup file: %w", err)
}
return nil
}

@ -0,0 +1,105 @@
package wakeup
import (
"fmt"
"io"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
type procfsMock struct{}
func (p *procfsMock) ACPIWakeup() (io.ReadCloser, error) {
const testFileRaw = `Device S-state Status Sysfs node
PEG0 S3 *disabled
EC S4 *disabled platform:PNP0C09:00
HDEF S3 *disabled pci:0000:00:1b.0
RP01 S3 *enabled pci:0000:00:1c.0
RP02 S3 *enabled pci:0000:00:1c.1
RP03 S3 *enabled pci:0000:00:1c.2
ARPT S4 *enabled pci:0000:03:00.0
RP05 S3 *enabled pci:0000:00:1c.4
RP06 S3 *enabled pci:0000:00:1c.5
SPIT S3 *disabled spi:spi-APP000D:00
XHC1 S3 *disabled pci:0000:00:14.0
ADP1 S4 *disabled platform:ACPI0003:00
LID0 S4 *disabled platform:PNP0C0D:00
`
return io.NopCloser(strings.NewReader(testFileRaw)), nil
}
func (p *procfsMock) ACPIWakeupWrite() (io.WriteCloser, error) {
return nil, fmt.Errorf("not implemented")
}
func TestSomething(t *testing.T) {
tt := []struct {
name string
wantStatus Status
wantDevices []string
}{
{
name: "Read and parse successfully",
wantStatus: StatusEnabled,
wantDevices: []string{
"RP01",
"RP02",
"RP03",
"RP05",
"RP06",
"ARPT",
},
},
{
name: "Read and parse disabled successfully",
wantStatus: StatusDisabled,
wantDevices: []string{
"PEG0",
"EC",
"HDEF",
"SPIT",
"XHC1",
"ADP1",
"LID0",
},
},
{
name: "Read and parse all successfully",
wantStatus: StatusAll,
wantDevices: []string{
"RP01",
"RP02",
"RP03",
"RP05",
"RP06",
"ARPT",
"PEG0",
"EC",
"HDEF",
"SPIT",
"XHC1",
"ADP1",
"LID0",
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
c := NewWakeupController()
c.ProcfsProvider = &procfsMock{}
devices, err := c.GetWakeupDevices(tc.wantStatus)
assert.NoError(t, err)
assert.Len(t, devices, len(tc.wantDevices))
for i, wantDevice := range tc.wantDevices {
assert.Contains(t, devices, wantDevice, "device %d not found", i)
}
})
}
}
Loading…
Cancel
Save