Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Local flash #97

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions osfv_cli/src/osfv/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import osfv.libs.utils as utils
import pexpect
import requests
import sys
from osfv.libs.rte import RTE
from osfv.libs.snipeit_api import SnipeIT
from osfv.libs.sonoff_api import SonoffDevice
Expand Down Expand Up @@ -857,6 +858,31 @@ def main():
"erase", help="Erase DUT flash with flashrom"
)

local_parser = flash_subparsers.add_parser(
"local", help="Locally flash the DUT (like flash.sh) using local GPIO toggling & flashrom"
)
local_subparsers = local_parser.add_subparsers(dest="local_cmd", help="Local flash commands")

local_probe_parser = local_subparsers.add_parser("probe", help="Probe flash locally")

local_read_parser = local_subparsers.add_parser("read", help="Read flash locally")
local_read_parser.add_argument(
"--rom",
type=str,
default="read.rom",
help="Output file for reading flash content (default: read.rom)",
)

local_write_parser = local_subparsers.add_parser("write", help="Write flash locally")
local_write_parser.add_argument(
"--rom",
type=str,
default="write.rom",
help="Firmware file to write to flash (default: write.rom)",
)

local_erase_parser = local_subparsers.add_parser("erase", help="Erase flash locally")

args = parser.parse_args()

snipeit_api = SnipeIT()
Expand Down Expand Up @@ -981,6 +1007,45 @@ def main():
flash_write(rte, args)
elif args.flash_cmd == "erase":
flash_erase(rte, args)
elif args.flash_cmd == "local":
# We have to see which subcommand was chosen: probe/read/write/erase
from osfv.libs.localflash import LocalFlasher, LocalFlashError

# User sets voltage, flashrom params, etc.
local_flasher = LocalFlasher(voltage="1.8V", flashrom_params="")

# check which local_cmd subcommand was used
if args.local_cmd == "probe":
try:
local_flasher.cmd_probe()
except LocalFlashError as e:
print(f"[ERROR] {e}")
sys.exit(1)

elif args.local_cmd == "read":
try:
local_flasher.cmd_read(args.rom)
except LocalFlashError as e:
print(f"[ERROR] {e}")
sys.exit(1)

elif args.local_cmd == "write":
try:
local_flasher.cmd_write(args.rom)
except LocalFlashError as e:
print(f"[ERROR] {e}")
sys.exit(1)

elif args.local_cmd == "erase":
try:
local_flasher.cmd_erase()
except LocalFlashError as e:
print(f"[ERROR] {e}")
sys.exit(1)

else:
print("No local flash subcommand chosen. See --help.")
sys.exit(1)

if not args.skip_snipeit:
if already_checked_out:
Expand Down
134 changes: 134 additions & 0 deletions osfv_cli/src/osfv/libs/localflash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import os
import time
import subprocess
import sys


class LocalFlashError(Exception):
pass


class LocalFlasher:
"""
A local flasher:
- toggles GPIO under /sys/class/gpio/gpioX/value
- calls flashrom with local programmer
"""

# same constants as in flash.sh
GPIO_SPI_VOLTAGE = 517 # 0 => 1.8V, 1 => 3.3V
GPIO_SPI_LINES = 516 # 1 => lines on
GPIO_SPI_VCC = 518 # 1 => VCC on

FLASHROM_PROGRAMMER = "linux_spi:dev=/dev/spidev1.0,spispeed=16000"

def __init__(self, voltage="1.8V", flashrom_params=""):
"""
:param voltage: "1.8V" or "3.3V"
:param flashrom_params: extra flashrom args (optional)
"""
self.voltage = voltage
self.flashrom_params = flashrom_params

def flashrom_cmd(self, extra_args):
"""
Runs flashrom with the chosen programmer + any extra args
"""
cmd = [
"flashrom",
"-p",
f"{self.FLASHROM_PROGRAMMER}",
]
if self.flashrom_params:
cmd.extend(self.flashrom_params.split())
cmd.extend(extra_args.split())

print(f"[localflash] Running: {' '.join(cmd)}")
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError as e:
raise LocalFlashError(f"flashrom failed with code {e.returncode}")

def set_gpio_value(self, gpio_no, value_str):
"""
Writes '0' or '1' to /sys/class/gpio/gpioX/value
"""
path = f"/sys/class/gpio/gpio{gpio_no}/value"
if not os.path.exists(path):
raise LocalFlashError(f"GPIO {gpio_no} not exported or not found at {path}")

# Convert '0'/'1' from the given 'value_str' if needed
if value_str == "0" or value_str == "1":
val = value_str
else:
raise LocalFlashError(f"Bad gpio value: {value_str}")

print(f"[localflash] echo {val} > {path}")
with open(path, "w") as f:
f.write(val)

def spi_on(self):
"""
Equivalent to flash.sh's `spiON()`:
- sets voltage (gpio 517) to 0 => 1.8V or 1 => 3.3V
- sets gpio 516 => 1 (SPI lines on)
- sets gpio 518 => 1 (VCC on)
"""
if self.voltage == "3.3V":
print("[localflash] Setting SPI VCC to 3.3V")
self.set_gpio_value(self.GPIO_SPI_VOLTAGE, "1")
else:
print("[localflash] Setting SPI VCC to 1.8V")
self.set_gpio_value(self.GPIO_SPI_VOLTAGE, "0")

time.sleep(1)
print("[localflash] SPI lines on")
self.set_gpio_value(self.GPIO_SPI_LINES, "1")

time.sleep(1)
print("[localflash] SPI Vcc on")
self.set_gpio_value(self.GPIO_SPI_VCC, "1")

time.sleep(2)

def spi_off(self):
"""
Equivalent to flash.sh's `spiOFF()`.
"""
print("[localflash] SPI Vcc off")
self.set_gpio_value(self.GPIO_SPI_VCC, "0")
print("[localflash] SPI lines off")
self.set_gpio_value(self.GPIO_SPI_LINES, "0")
# set voltage to 0 => 1.8 again, or no
self.set_gpio_value(self.GPIO_SPI_VOLTAGE, "0")

def cmd_probe(self):
# flashrom -p <programmer> ...
self.spi_on()
print("[localflash] Probing flash with flashrom")
self.flashrom_cmd("")
self.spi_off()

def cmd_read(self, out_file):
# flashrom -p <programmer> -r out_file
self.spi_on()
print(f"[localflash] Reading flash -> {out_file}")
self.flashrom_cmd(f"-r {out_file} -V")
self.spi_off()

def cmd_write(self, in_file):
# flashrom -p <programmer> -w in_file
if not os.path.isfile(in_file):
raise LocalFlashError(f"Input file {in_file} does not exist!")
self.spi_on()
print(f"[localflash] Writing {in_file} to flash...")
self.flashrom_cmd(f"-w {in_file}")
self.spi_off()

def cmd_erase(self):
# flashrom -p <programmer> -E
self.spi_on()
print("[localflash] Erasing entire flash...")
self.flashrom_cmd("-E")
self.spi_off()