blob: d06171592d61b4ac010bb7bcc8cc40e322649861 [file] [log] [blame]
Dennis Kempin76f89c72014-05-15 15:53:41 -07001# 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 Cutts69fc2be2020-01-22 18:03:21 -08007from __future__ import absolute_import
8from __future__ import division
Harry Cutts0edf1572020-01-21 15:42:10 -08009from __future__ import print_function
10
Dennis Kempin6fd10e52014-05-29 15:49:43 -070011from mtlib.util import Path, RequiredRegex, Execute, SafeExecute, GitRepo
Dennis Kempin76f89c72014-05-15 15:53:41 -070012from tempfile import NamedTemporaryFile
13import tarfile
14import os
15import io
16import time
17
18src_dir = Path("/mnt/host/source/src/")
Dennis Kempin6fd10e52014-05-29 15:49:43 -070019script_dir = Path(__file__).parent
20templates_dir = script_dir / "templates"
21ebuild_template_file = templates_dir / "ebuild.template"
Dennis Kempin76f89c72014-05-15 15:53:41 -070022
23class FirmwareException(Exception):
24 pass
25
26class 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
84class 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 Mooneyf762ef22014-08-12 10:53:12 -0700100 self.bcsname = "bcs-{}-private".format(variant if variant else board)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700101
Charlie Mooneyf762ef22014-08-12 10:53:12 -0700102 self.ebuild_name = "chromeos-touch-firmware-{}".format(
103 variant if variant else board)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700104 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 Kempin6fd10e52014-05-29 15:49:43 -0700112 self.bcs_url = None
113 self.ebuild_version = None
Dennis Kempin76f89c72014-05-15 15:53:41 -0700114
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700115 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 Kempin76f89c72014-05-15 15:53:41 -0700119
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700120 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 Kempin76f89c72014-05-15 15:53:41 -0700126
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700127 def _UpdateVersion(self, version):
128 self.ebuild_version = version
129 self.ebuild_symlink = self.ebuild_dir / "{}-{}.ebuild".format(
130 self.ebuild_name, version)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700131 url = "gs://chromeos-binaries/HOME/{}/{}/chromeos-base/{}/{}-{}.tbz2"
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700132 self.bcs_url = url.format(self.bcsname, self.overlay.basename,
Dennis Kempin76f89c72014-05-15 15:53:41 -0700133 self.ebuild_name, self.ebuild_name,
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700134 version)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700135
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 Kempin6fd10e52014-05-29 15:49:43 -0700147 if not self.ebuild_version:
148 return
149
Dennis Kempin76f89c72014-05-15 15:53:41 -0700150 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 Mooneyf762ef22014-08-12 10:53:12 -0700170 tar_name = "{}-{}".format(self.ebuild_name, version)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700171 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 Cutts69fc2be2020-01-22 18:03:21 -0800179 info.mode = 0o755
Dennis Kempin76f89c72014-05-15 15:53:41 -0700180 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 Kempin6fd10e52014-05-29 15:49:43 -0700193 if old_symlink and old_symlink != new_symlink:
Dennis Kempin76f89c72014-05-15 15:53:41 -0700194 self.ebuild_repo.Move(old_symlink, new_symlink)
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700195 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 Kempin76f89c72014-05-15 15:53:41 -0700199
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 Kempin6fd10e52014-05-29 15:49:43 -0700216 def UpdateSrcInstall(self, remote, dosym_lines):
Dennis Kempin76f89c72014-05-15 15:53:41 -0700217 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 Frysinger97d6b3b2017-09-27 12:22:09 -0400238 out.write("\tinsinto /\ndoins -r */*\n")
Dennis Kempin76f89c72014-05-15 15:53:41 -0700239
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700240 for line in dosym_lines:
241 out.write("\t{}\n".format(line))
Dennis Kempin76f89c72014-05-15 15:53:41 -0700242
243 out.write("}\n")
244 out.write(ebuild[end:].strip())
245 out.close()
246
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700247 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 Kempin76f89c72014-05-15 15:53:41 -0700277 def VerifySymlinks(self, remote):
278 valid = True
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700279 for firmware in self.GetExistingBinaries():
280 cmd = "readlink -f /lib/firmware/{}".format(firmware.symlink_name)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700281 target = remote.Execute(cmd)
282
283 if not target:
284 msg = "Symlink for firmware '{}' does not exist on device"
Harry Cutts0edf1572020-01-21 15:42:10 -0800285 print(msg.format(firmware))
Dennis Kempin76f89c72014-05-15 15:53:41 -0700286 valid = False
287
288 target = Path(target)
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700289 if target.basename != firmware.device_file.basename:
Dennis Kempin76f89c72014-05-15 15:53:41 -0700290 msg = "Symlink for firmware '{}' does not point to the right file"
Harry Cutts0edf1572020-01-21 15:42:10 -0800291 print(msg.format(firmware))
Dennis Kempin76f89c72014-05-15 15:53:41 -0700292 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 Cutts0edf1572020-01-21 15:42:10 -0800297 print(msg.format(firmware.device_file))
Dennis Kempin76f89c72014-05-15 15:53:41 -0700298 valid = False
299
300 return valid
301
302 def VerifyFirmwareVersions(self, remote):
303 device_info = remote.GetDeviceInfo(refresh=True)
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700304 binaries = dict([(b.hw_version, b) for b in self.GetExistingBinaries()])
Dennis Kempin76f89c72014-05-15 15:53:41 -0700305
306 valid = True
307 for device in device_info.touch_devices.values():
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700308 if device.hw_version not in binaries:
Dennis Kempin76f89c72014-05-15 15:53:41 -0700309 continue
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700310 firmware = binaries[device.hw_version]
Dennis Kempin76f89c72014-05-15 15:53:41 -0700311 if firmware.fw_version != device.fw_version:
Harry Cutts0edf1572020-01-21 15:42:10 -0800312 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 Kempin76f89c72014-05-15 15:53:41 -0700315 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