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