summaryrefslogtreecommitdiff
path: root/cxmanage_api/ubootenv.py
diff options
context:
space:
mode:
Diffstat (limited to 'cxmanage_api/ubootenv.py')
-rw-r--r--cxmanage_api/ubootenv.py255
1 files changed, 255 insertions, 0 deletions
diff --git a/cxmanage_api/ubootenv.py b/cxmanage_api/ubootenv.py
new file mode 100644
index 0000000..b5b8272
--- /dev/null
+++ b/cxmanage_api/ubootenv.py
@@ -0,0 +1,255 @@
+# Copyright (c) 2012, Calxeda Inc.
+#
+# 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 Calxeda 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 HOLDERS 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.
+
+
+import struct
+
+from cxmanage_api.simg import has_simg, get_simg_contents
+from cxmanage_api.crc32 import get_crc32
+from cxmanage_api.cx_exceptions import UnknownBootCmdError
+
+
+ENVIRONMENT_SIZE = 8192
+UBOOTENV_V1_VARIABLES = ["bootcmd_default", "bootcmd_sata", "bootcmd_pxe",
+ "bootdevice"]
+UBOOTENV_V2_VARIABLES = ["bootcmd0", "init_scsi", "bootcmd_scsi", "init_pxe",
+ "bootcmd_pxe", "devnum"]
+
+
+class UbootEnv:
+ """Represents a U-Boot Environment.
+
+ >>> from cxmanage_api.ubootenv import UbootEnv
+ >>> uboot = UbootEnv()
+
+ :param contents: UBootEnvironment contnents.
+ :type contents: string
+
+ """
+
+ def __init__(self, contents=None):
+ """Default constructor for the UbootEnv class."""
+ self.variables = {}
+
+ if (contents != None):
+ if (has_simg(contents)):
+ contents = get_simg_contents(contents)
+
+ contents = contents.rstrip("%c%c" % (chr(0), chr(255)))[4:]
+ lines = contents.split(chr(0))
+ for line in lines:
+ part = line.partition("=")
+ self.variables[part[0]] = part[2]
+
+ def set_boot_order(self, boot_args):
+ """Sets the boot order specified in the uboot environment.
+
+ >>> uboot.set_boot_order(boot_args=['disk', 'pxe'])
+
+ .. note::
+ * Valid Args:
+ pxe - boot from pxe server\n
+ disk - boot from default sata device\n
+ diskX - boot from sata device X\n
+ diskX:Y - boot from sata device X, partition Y\n
+ retry - retry last boot device indefinitely\n
+ reset - reset A9\n
+
+ :param boot_args: Boot args (boot order). A list of strings.
+ :type boot_args: list
+
+ :raises ValueError: If an invalid boot device is specified.
+ :raises ValueError: If 'retry' and 'reset' args are used together.
+ :raises Exception: If the u-boot environment is unrecognized
+
+ """
+ validate_boot_args(boot_args)
+ if boot_args == self.get_boot_order():
+ return
+
+ commands = []
+ retry = False
+ reset = False
+
+ if all(x in self.variables for x in UBOOTENV_V1_VARIABLES):
+ version = 1
+ elif all(x in self.variables for x in UBOOTENV_V2_VARIABLES):
+ version = 2
+ else:
+ raise Exception("Unrecognized u-boot environment")
+
+ for arg in boot_args:
+ if arg == "retry":
+ retry = True
+ elif arg == "reset":
+ reset = True
+ elif version == 1:
+ if arg == "pxe":
+ commands.append("run bootcmd_pxe")
+ elif arg == "disk":
+ commands.append("run bootcmd_sata")
+ elif arg.startswith("disk"):
+ try:
+ dev, part = map(int, arg[4:].split(":"))
+ bootdevice = "%i:%i" % (dev, part)
+ except ValueError:
+ bootdevice = str(int(arg[4:]))
+ commands.append("setenv bootdevice %s && run bootcmd_sata"
+ % bootdevice)
+ elif version == 2:
+ if arg == "pxe":
+ commands.append("run init_pxe && run bootcmd_pxe")
+ elif arg == "disk":
+ commands.append("run init_scsi && run bootcmd_scsi")
+ elif arg.startswith("disk"):
+ try:
+ dev, part = map(int, arg[4:].split(":"))
+ bootdevice = "%i:%i" % (dev, part)
+ except ValueError:
+ bootdevice = str(int(arg[4:]))
+ commands.append(
+ "setenv devnum %s && run init_scsi && run bootcmd_scsi"
+ % bootdevice)
+
+ if retry and reset:
+ raise ValueError("retry and reset are mutually exclusive")
+ elif retry:
+ commands[-1] = "while true\ndo\n%s\nsleep 1\ndone" % commands[-1]
+ elif reset:
+ commands.append("reset")
+
+ if version == 1:
+ self.variables["bootcmd_default"] = "; ".join(commands)
+ else:
+ self.variables["bootcmd0"] = "; ".join(commands)
+
+ def get_boot_order(self):
+ """Gets the boot order specified in the uboot environment.
+
+ >>> uboot.get_boot_order()
+ ['disk', 'pxe']
+
+ :returns: Boot order for this U-Boot Environment.
+ :rtype: string
+
+ :raises UnknownBootCmdError: If a boot command is unrecognized.
+
+ """
+ boot_args = []
+
+ if self.variables["bootcmd0"] == "run boot_iter":
+ for target in self.variables["boot_targets"].split():
+ if target == "pxe":
+ boot_args.append("pxe")
+ elif target == "scsi":
+ boot_args.append("disk")
+ else:
+ raise UnknownBootCmdError("Unrecognized boot target: %s"
+ % target)
+ else:
+ if "bootcmd_default" in self.variables:
+ commands = self.variables["bootcmd_default"].split("; ")
+ else:
+ commands = self.variables["bootcmd0"].split("; ")
+
+ retry = False
+ for command in commands:
+ if command.startswith("while true"):
+ retry = True
+ command = command.split("\n")[2]
+
+ if command in ["run bootcmd_pxe",
+ "run init_pxe && run bootcmd_pxe"]:
+ boot_args.append("pxe")
+ elif command in ["run bootcmd_sata",
+ "run init_scsi && run bootcmd_scsi"]:
+ boot_args.append("disk")
+ elif (command.startswith("setenv bootdevice") or
+ command.startswith("setenv devnum")):
+ boot_args.append("disk%s" % command.split()[2])
+ elif (command == "reset"):
+ boot_args.append("reset")
+ break
+ else:
+ raise UnknownBootCmdError("Unrecognized boot command: %s"
+ % command)
+
+ if retry:
+ boot_args.append("retry")
+ break
+
+ if not boot_args:
+ boot_args = ["none"]
+
+ validate_boot_args(boot_args) # sanity check
+ return boot_args
+
+ def get_contents(self):
+ """Returns a raw string representation of the uboot environment.
+
+ >>> uboot.get_contents()
+ 'j4\x88\xb7bootcmd_default=run bootcmd_sata; run bootcmd_pxe ... '
+ >>> #
+ >>> # Output trimmed for brevity ...
+ >>> #
+
+ :returns: Raw string representation of the UBoot Environment.
+ :rtype: string
+
+ """
+ contents = ""
+ # Add variables
+ for variable in self.variables:
+ contents += "%s=%s\0" % (variable, self.variables[variable])
+ contents += "\0"
+ # Add padding to end
+ contents += "".join([chr(255)
+ for _ in range(ENVIRONMENT_SIZE - len(contents) - 4)])
+ # Add crc32 to beginning
+ crc32 = get_crc32(contents, 0xFFFFFFFF) ^ 0xFFFFFFFF
+ contents = struct.pack("<I", crc32) + contents
+ return contents
+
+
+def validate_boot_args(boot_args):
+ """ Validate boot arguments. Raises a ValueError if the args are invalid."""
+ for arg in boot_args:
+ if arg in ["retry", "reset", "pxe", "disk", "none"]:
+ continue
+ elif arg.startswith("disk"):
+ try:
+ map(int, arg[4:].split(":"))
+ except ValueError:
+ try:
+ int(arg[4:])
+ except ValueError:
+ raise ValueError("Invalid boot arg: %s" % arg)
+ else:
+ raise ValueError("Invalid boot arg: %s" % arg)