Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 1 | # Copyright (c) 2014 The Chromium OS Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | """Provides tools for packaging, installing and testing firmware.""" |
| 6 | |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 7 | from mtlib.util import Path, RequiredRegex, Execute, SafeExecute, GitRepo |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 8 | from tempfile import NamedTemporaryFile |
| 9 | import tarfile |
| 10 | import os |
| 11 | import io |
| 12 | import time |
| 13 | |
| 14 | src_dir = Path("/mnt/host/source/src/") |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 15 | script_dir = Path(__file__).parent |
| 16 | templates_dir = script_dir / "templates" |
| 17 | ebuild_template_file = templates_dir / "ebuild.template" |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 18 | |
| 19 | class FirmwareException(Exception): |
| 20 | pass |
| 21 | |
| 22 | class FirmwareBinary(object): |
| 23 | """.""" |
| 24 | def __init__(self, filename, fileobj=None, symlink=None): |
| 25 | self.hw_version = None |
| 26 | self.fw_version = None |
| 27 | self.symlink_name = None |
| 28 | |
| 29 | if not fileobj: |
| 30 | fileobj = open(filename, "rb") |
| 31 | self.data = fileobj.read() |
| 32 | |
| 33 | if symlink: |
| 34 | self.symlink_name = symlink |
| 35 | |
| 36 | name_regex = RequiredRegex("([0-9.a-zA-Z]+)_([0-9.a-zA-Z]+)\\.bin") |
| 37 | match = name_regex.Match(filename, must_succeed=False) |
| 38 | if match: |
| 39 | self.hw_version = match.group(1) |
| 40 | self.fw_version = match.group(2) |
| 41 | |
| 42 | self.filename = filename |
| 43 | self.device_file = Path("/opt/google/touch/firmware", filename) |
| 44 | |
| 45 | def UpdateName(self, hw_version, fw_version): |
| 46 | name = "{}_{}.bin".format(hw_version, fw_version) |
| 47 | os.rename(self.filename, name) |
| 48 | self.filename = name |
| 49 | |
| 50 | def ForceUpdate(self, device, remote): |
| 51 | remote.RemountWriteable() |
| 52 | |
| 53 | symlink = Path("/lib/firmware", device.symlink) |
| 54 | symlink_bak = Path(str(symlink) + ".bak") |
| 55 | |
| 56 | target = Path("/opt/google/touch/firmware/force_update.fw") |
| 57 | device.symlink = symlink.basename |
| 58 | |
| 59 | remote.Write(str(target), self.data) |
| 60 | try: |
| 61 | remote.SafeExecute(["mv", str(symlink), str(symlink_bak)]) |
| 62 | try: |
| 63 | remote.SafeExecute(["ln", "-s", str(target), str(symlink)]) |
| 64 | device.ForceFirmwareUpdate(remote) |
| 65 | finally: |
| 66 | remote.SafeExecute(["mv", str(symlink_bak), str(symlink)]) |
| 67 | finally: |
| 68 | remote.SafeExecute(["rm", str(target)]) |
| 69 | |
| 70 | def __str__(self): |
| 71 | symlink = "Unknown" |
| 72 | if self.symlink_name: |
| 73 | symlink = "/lib/firmware/" + self.symlink_name |
| 74 | return "%s @%s" % (self.filename, symlink) |
| 75 | |
| 76 | def __repr__(self): |
| 77 | return str(self) |
| 78 | |
| 79 | |
| 80 | class FirmwarePackage(object): |
| 81 | """Helper class to deal with firmware installation on devices.""" |
| 82 | name = "firmware" |
| 83 | |
| 84 | def __init__(self, board, variant): |
| 85 | self.board = board |
| 86 | self.variant = variant |
| 87 | self.binaries = {} |
| 88 | |
| 89 | # determine path and name of touch firmware ebuild file |
| 90 | if variant: |
| 91 | overlay_name = "overlay-variant-{}-{}-private".format( |
| 92 | board, variant) |
| 93 | else: |
| 94 | overlay_name = "overlay-{}-private".format(board) |
| 95 | self.overlay = src_dir / "private-overlays" / overlay_name |
Charlie Mooney | f762ef2 | 2014-08-12 10:53:12 -0700 | [diff] [blame^] | 96 | self.bcsname = "bcs-{}-private".format(variant if variant else board) |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 97 | |
Charlie Mooney | f762ef2 | 2014-08-12 10:53:12 -0700 | [diff] [blame^] | 98 | self.ebuild_name = "chromeos-touch-firmware-{}".format( |
| 99 | variant if variant else board) |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 100 | self.ebuild_dir = self.overlay / "chromeos-base" / self.ebuild_name |
| 101 | self.ebuild_repo = GitRepo(self.ebuild_dir) |
| 102 | |
| 103 | self.ebuild_file = self.ebuild_dir / "{}-0.0.1.ebuild".format( |
| 104 | self.ebuild_name) |
| 105 | |
| 106 | # look for symlink to ebuild file |
| 107 | self.ebuild_symlink = None |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 108 | self.bcs_url = None |
| 109 | self.ebuild_version = None |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 110 | |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 111 | if self.ebuild_file.exists: |
| 112 | for symlink in self.ebuild_dir.ListDir(): |
| 113 | if symlink.is_link and symlink.basename.startswith(self.ebuild_name): |
| 114 | self.ebuild_symlink = symlink |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 115 | |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 116 | if self.ebuild_symlink: |
| 117 | # extract ebuild version from symlink name |
| 118 | regex = "{}-([0-9a-zA-Z_\\-\\.]*).ebuild" |
| 119 | regex = RequiredRegex(regex.format(self.ebuild_name)) |
| 120 | match = regex.Search(self.ebuild_symlink.basename) |
| 121 | self._UpdateVersion(match.group(1)) |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 122 | |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 123 | def _UpdateVersion(self, version): |
| 124 | self.ebuild_version = version |
| 125 | self.ebuild_symlink = self.ebuild_dir / "{}-{}.ebuild".format( |
| 126 | self.ebuild_name, version) |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 127 | url = "gs://chromeos-binaries/HOME/{}/{}/chromeos-base/{}/{}-{}.tbz2" |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 128 | self.bcs_url = url.format(self.bcsname, self.overlay.basename, |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 129 | self.ebuild_name, self.ebuild_name, |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 130 | version) |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 131 | |
| 132 | def _ExtractSymlinkfromEbuild(self, firmware): |
| 133 | ebuild = open(str(self.ebuild_file), "r").read() |
| 134 | regex = "dosym \"{}\" \"/lib/firmware/([a-zA-Z0-9_\\-.]+)\"" |
| 135 | regex = RequiredRegex(regex.format(firmware.device_file)) |
| 136 | match = regex.Search(ebuild, must_succeed=False) |
| 137 | if match: |
| 138 | return match.group(1) |
| 139 | else: |
| 140 | return None |
| 141 | |
| 142 | def GetExistingBinaries(self): |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 143 | if not self.ebuild_version: |
| 144 | return |
| 145 | |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 146 | tar_file = NamedTemporaryFile("rb") |
| 147 | res = Execute(["gsutil", "cp", self.bcs_url, tar_file.name]) |
| 148 | if not res: |
| 149 | return |
| 150 | tar_file.seek(0) |
| 151 | tar = tarfile.open(fileobj=tar_file) |
| 152 | |
| 153 | for member in tar.getmembers(): |
| 154 | if not member.isfile(): |
| 155 | continue |
| 156 | name = os.path.basename(member.name) |
| 157 | fileobj = tar.extractfile(member) |
| 158 | binary = FirmwareBinary(name, fileobj) |
| 159 | binary.symlink_name = self._ExtractSymlinkfromEbuild(binary) |
| 160 | yield binary |
| 161 | |
| 162 | def AddBinary(self, binary): |
| 163 | self.binaries[binary.hw_version] = binary |
| 164 | |
| 165 | def GenerateBCSPackage(self, version): |
Charlie Mooney | f762ef2 | 2014-08-12 10:53:12 -0700 | [diff] [blame^] | 166 | tar_name = "{}-{}".format(self.ebuild_name, version) |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 167 | tar_file = "{}.tbz2".format(tar_name) |
| 168 | |
| 169 | tar = tarfile.open(tar_file, "w:bz2") |
| 170 | for binary in self.binaries.values(): |
| 171 | data = io.BytesIO(binary.data) |
| 172 | path = tar_name + str(binary.device_file) |
| 173 | info = tarfile.TarInfo(path) |
| 174 | info.size = len(binary.data) |
| 175 | info.mode = 0755 |
| 176 | info.uid = 0 |
| 177 | info.gid = 0 |
| 178 | info.mtime = time.time() |
| 179 | info.uname = "root" |
| 180 | info.gname = "root" |
| 181 | tar.addfile(info, data) |
| 182 | return Path(tar_file) |
| 183 | |
| 184 | def UpdateVersionSymlink(self, version): |
| 185 | new_symlink = self.ebuild_dir / "{}-{}.ebuild".format( |
| 186 | self.ebuild_name, version) |
| 187 | |
| 188 | old_symlink = self.ebuild_symlink |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 189 | if old_symlink and old_symlink != new_symlink: |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 190 | self.ebuild_repo.Move(old_symlink, new_symlink) |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 191 | if not new_symlink.is_link: |
| 192 | SafeExecute(["ln", "-s", self.ebuild_file.basename, str(new_symlink)]) |
| 193 | self.ebuild_repo.Add(new_symlink) |
| 194 | self._UpdateVersion(version) |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 195 | |
| 196 | def UpdateBinarySymlinks(self, remote): |
| 197 | device_info = remote.GetDeviceInfo() |
| 198 | devices = device_info.touch_devices |
| 199 | for firmware in self.binaries.values(): |
| 200 | if firmware.symlink_name: |
| 201 | continue |
| 202 | if firmware.hw_version not in devices: |
| 203 | msg = "Cannot find device for binary {}" |
| 204 | raise Exception(msg.format(firmware)) |
| 205 | device = devices[firmware.hw_version] |
| 206 | firmware.symlink_name = device.symlink |
| 207 | |
| 208 | symlink_names = [b.symlink_name for b in self.binaries.values()] |
| 209 | if len(set(symlink_names)) != len(symlink_names): |
| 210 | raise Exception("Duplicate symlink names for firmwares found") |
| 211 | |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 212 | def UpdateSrcInstall(self, remote, dosym_lines): |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 213 | ebuild = self.ebuild_file.Read() |
| 214 | |
| 215 | install_idx = ebuild.find("src_install") |
| 216 | begin = ebuild.find("{", install_idx) |
| 217 | |
| 218 | # find closing bracket |
| 219 | brackets = 0 |
| 220 | end = len(ebuild) |
| 221 | for end in range(begin, len(ebuild)): |
| 222 | if ebuild[end] == "{": |
| 223 | brackets = brackets + 1 |
| 224 | elif ebuild[end] == "}": |
| 225 | brackets = brackets - 1 |
| 226 | if brackets == 0: |
| 227 | end = end + 1 |
| 228 | break |
| 229 | |
| 230 | # write ebuild with new src_install method |
| 231 | out = self.ebuild_file.Open("w") |
| 232 | out.write(ebuild[:begin]) |
| 233 | out.write("{\n") |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 234 | out.write("\tcros-binary_src_install\n\n") |
| 235 | |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 236 | for line in dosym_lines: |
| 237 | out.write("\t{}\n".format(line)) |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 238 | |
| 239 | out.write("}\n") |
| 240 | out.write(ebuild[end:].strip()) |
| 241 | out.close() |
| 242 | |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 243 | def GenerateEbuildFile(self, remote, dosym_lines): |
| 244 | rdepend = "\tchromeos-base/touch_updater" |
| 245 | if self.variant: |
| 246 | line = "\t!chromeos-base/chromeos-touch-firmware-{}" |
| 247 | rdepend += line.format(self.board) |
| 248 | |
| 249 | template = ebuild_template_file.Read() |
| 250 | variables = { |
| 251 | "year": time.strftime("%Y"), |
| 252 | "rdepend": rdepend, |
| 253 | "bcs": self.bcsname, |
| 254 | "overlay": self.overlay.basename, |
| 255 | "dosym_lines": "\n\t".join(dosym_lines)} |
| 256 | ebuild = template.format(**variables) |
| 257 | self.ebuild_file.Write(ebuild) |
| 258 | |
| 259 | def UpdateEbuildFile(self, remote, regenerate=False): |
| 260 | self.UpdateBinarySymlinks(remote) |
| 261 | |
| 262 | dosym_lines = [] |
| 263 | for firmware in self.binaries.values(): |
| 264 | line = "dosym \"%s\" \"/lib/firmware/%s\"" |
| 265 | dosym_lines.append(line % (firmware.device_file, firmware.symlink_name)) |
| 266 | |
| 267 | if regenerate or not self.ebuild_file.exists: |
| 268 | self.GenerateEbuildFile(remote, dosym_lines) |
| 269 | else: |
| 270 | self.UpdateSrcInstall(remote, dosym_lines) |
| 271 | |
| 272 | |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 273 | def VerifySymlinks(self, remote): |
| 274 | valid = True |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 275 | for firmware in self.GetExistingBinaries(): |
| 276 | cmd = "readlink -f /lib/firmware/{}".format(firmware.symlink_name) |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 277 | target = remote.Execute(cmd) |
| 278 | |
| 279 | if not target: |
| 280 | msg = "Symlink for firmware '{}' does not exist on device" |
| 281 | print msg.format(firmware) |
| 282 | valid = False |
| 283 | |
| 284 | target = Path(target) |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 285 | if target.basename != firmware.device_file.basename: |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 286 | msg = "Symlink for firmware '{}' does not point to the right file" |
| 287 | print msg.format(firmware) |
| 288 | valid = False |
| 289 | |
| 290 | cmd = "ls {}".format(firmware.device_file) |
| 291 | if remote.Execute(cmd) is False: |
| 292 | msg = "Firmware file {} does not exist on device" |
| 293 | print msg.format(firmware.device_file) |
| 294 | valid = False |
| 295 | |
| 296 | return valid |
| 297 | |
| 298 | def VerifyFirmwareVersions(self, remote): |
| 299 | device_info = remote.GetDeviceInfo(refresh=True) |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 300 | binaries = dict([(b.hw_version, b) for b in self.GetExistingBinaries()]) |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 301 | |
| 302 | valid = True |
| 303 | for device in device_info.touch_devices.values(): |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 304 | if device.hw_version not in binaries: |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 305 | continue |
Dennis Kempin | 6fd10e5 | 2014-05-29 15:49:43 -0700 | [diff] [blame] | 306 | firmware = binaries[device.hw_version] |
Dennis Kempin | 76f89c7 | 2014-05-15 15:53:41 -0700 | [diff] [blame] | 307 | if firmware.fw_version != device.fw_version: |
| 308 | print "Device {} did not update correctly:".format(device.hw_version) |
| 309 | print "Device version {} != firmware version {}".format( |
| 310 | device.fw_version, firmware.fw_version) |
| 311 | valid = False |
| 312 | return valid |
| 313 | |
| 314 | def __str__(self): |
| 315 | res = " firmwares:\n" |
| 316 | for firmware in self.firmwares.values(): |
| 317 | res += " {}".format(firmware) |
| 318 | return res |