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