blob: b6d5a65c98b1ac412e914991172011a0eb6064eb [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
Dennis Kempin6fd10e52014-05-29 15:49:43 -07007from mtlib.util import Path, RequiredRegex, Execute, SafeExecute, GitRepo
Dennis Kempin76f89c72014-05-15 15:53:41 -07008from tempfile import NamedTemporaryFile
9import tarfile
10import os
11import io
12import time
13
14src_dir = Path("/mnt/host/source/src/")
Dennis Kempin6fd10e52014-05-29 15:49:43 -070015script_dir = Path(__file__).parent
16templates_dir = script_dir / "templates"
17ebuild_template_file = templates_dir / "ebuild.template"
Dennis Kempin76f89c72014-05-15 15:53:41 -070018
19class FirmwareException(Exception):
20 pass
21
22class 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
80class 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
Dennis Kempin6fd10e52014-05-29 15:49:43 -070096 self.bcsname = self.overlay.basename.replace("overlay-", "bcs-")
Dennis Kempin76f89c72014-05-15 15:53:41 -070097
98 self.ebuild_name = "chromeos-touch-firmware-{}".format(board)
99 self.ebuild_dir = self.overlay / "chromeos-base" / self.ebuild_name
100 self.ebuild_repo = GitRepo(self.ebuild_dir)
101
102 self.ebuild_file = self.ebuild_dir / "{}-0.0.1.ebuild".format(
103 self.ebuild_name)
104
105 # look for symlink to ebuild file
106 self.ebuild_symlink = None
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700107 self.bcs_url = None
108 self.ebuild_version = None
Dennis Kempin76f89c72014-05-15 15:53:41 -0700109
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700110 if self.ebuild_file.exists:
111 for symlink in self.ebuild_dir.ListDir():
112 if symlink.is_link and symlink.basename.startswith(self.ebuild_name):
113 self.ebuild_symlink = symlink
Dennis Kempin76f89c72014-05-15 15:53:41 -0700114
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700115 if self.ebuild_symlink:
116 # extract ebuild version from symlink name
117 regex = "{}-([0-9a-zA-Z_\\-\\.]*).ebuild"
118 regex = RequiredRegex(regex.format(self.ebuild_name))
119 match = regex.Search(self.ebuild_symlink.basename)
120 self._UpdateVersion(match.group(1))
Dennis Kempin76f89c72014-05-15 15:53:41 -0700121
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700122 def _UpdateVersion(self, version):
123 self.ebuild_version = version
124 self.ebuild_symlink = self.ebuild_dir / "{}-{}.ebuild".format(
125 self.ebuild_name, version)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700126 url = "gs://chromeos-binaries/HOME/{}/{}/chromeos-base/{}/{}-{}.tbz2"
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700127 self.bcs_url = url.format(self.bcsname, self.overlay.basename,
Dennis Kempin76f89c72014-05-15 15:53:41 -0700128 self.ebuild_name, self.ebuild_name,
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700129 version)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700130
131 def _ExtractSymlinkfromEbuild(self, firmware):
132 ebuild = open(str(self.ebuild_file), "r").read()
133 regex = "dosym \"{}\" \"/lib/firmware/([a-zA-Z0-9_\\-.]+)\""
134 regex = RequiredRegex(regex.format(firmware.device_file))
135 match = regex.Search(ebuild, must_succeed=False)
136 if match:
137 return match.group(1)
138 else:
139 return None
140
141 def GetExistingBinaries(self):
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700142 if not self.ebuild_version:
143 return
144
Dennis Kempin76f89c72014-05-15 15:53:41 -0700145 tar_file = NamedTemporaryFile("rb")
146 res = Execute(["gsutil", "cp", self.bcs_url, tar_file.name])
147 if not res:
148 return
149 tar_file.seek(0)
150 tar = tarfile.open(fileobj=tar_file)
151
152 for member in tar.getmembers():
153 if not member.isfile():
154 continue
155 name = os.path.basename(member.name)
156 fileobj = tar.extractfile(member)
157 binary = FirmwareBinary(name, fileobj)
158 binary.symlink_name = self._ExtractSymlinkfromEbuild(binary)
159 yield binary
160
161 def AddBinary(self, binary):
162 self.binaries[binary.hw_version] = binary
163
164 def GenerateBCSPackage(self, version):
165 tar_name = "chromeos-touch-firmware-{}-{}".format(self.board, version)
166 tar_file = "{}.tbz2".format(tar_name)
167
168 tar = tarfile.open(tar_file, "w:bz2")
169 for binary in self.binaries.values():
170 data = io.BytesIO(binary.data)
171 path = tar_name + str(binary.device_file)
172 info = tarfile.TarInfo(path)
173 info.size = len(binary.data)
174 info.mode = 0755
175 info.uid = 0
176 info.gid = 0
177 info.mtime = time.time()
178 info.uname = "root"
179 info.gname = "root"
180 tar.addfile(info, data)
181 return Path(tar_file)
182
183 def UpdateVersionSymlink(self, version):
184 new_symlink = self.ebuild_dir / "{}-{}.ebuild".format(
185 self.ebuild_name, version)
186
187 old_symlink = self.ebuild_symlink
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700188 if old_symlink and old_symlink != new_symlink:
Dennis Kempin76f89c72014-05-15 15:53:41 -0700189 self.ebuild_repo.Move(old_symlink, new_symlink)
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700190 if not new_symlink.is_link:
191 SafeExecute(["ln", "-s", self.ebuild_file.basename, str(new_symlink)])
192 self.ebuild_repo.Add(new_symlink)
193 self._UpdateVersion(version)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700194
195 def UpdateBinarySymlinks(self, remote):
196 device_info = remote.GetDeviceInfo()
197 devices = device_info.touch_devices
198 for firmware in self.binaries.values():
199 if firmware.symlink_name:
200 continue
201 if firmware.hw_version not in devices:
202 msg = "Cannot find device for binary {}"
203 raise Exception(msg.format(firmware))
204 device = devices[firmware.hw_version]
205 firmware.symlink_name = device.symlink
206
207 symlink_names = [b.symlink_name for b in self.binaries.values()]
208 if len(set(symlink_names)) != len(symlink_names):
209 raise Exception("Duplicate symlink names for firmwares found")
210
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700211 def UpdateSrcInstall(self, remote, dosym_lines):
Dennis Kempin76f89c72014-05-15 15:53:41 -0700212 ebuild = self.ebuild_file.Read()
213
214 install_idx = ebuild.find("src_install")
215 begin = ebuild.find("{", install_idx)
216
217 # find closing bracket
218 brackets = 0
219 end = len(ebuild)
220 for end in range(begin, len(ebuild)):
221 if ebuild[end] == "{":
222 brackets = brackets + 1
223 elif ebuild[end] == "}":
224 brackets = brackets - 1
225 if brackets == 0:
226 end = end + 1
227 break
228
229 # write ebuild with new src_install method
230 out = self.ebuild_file.Open("w")
231 out.write(ebuild[:begin])
232 out.write("{\n")
Dennis Kempin76f89c72014-05-15 15:53:41 -0700233 out.write("\tcros-binary_src_install\n\n")
234
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700235 for line in dosym_lines:
236 out.write("\t{}\n".format(line))
Dennis Kempin76f89c72014-05-15 15:53:41 -0700237
238 out.write("}\n")
239 out.write(ebuild[end:].strip())
240 out.close()
241
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700242 def GenerateEbuildFile(self, remote, dosym_lines):
243 rdepend = "\tchromeos-base/touch_updater"
244 if self.variant:
245 line = "\t!chromeos-base/chromeos-touch-firmware-{}"
246 rdepend += line.format(self.board)
247
248 template = ebuild_template_file.Read()
249 variables = {
250 "year": time.strftime("%Y"),
251 "rdepend": rdepend,
252 "bcs": self.bcsname,
253 "overlay": self.overlay.basename,
254 "dosym_lines": "\n\t".join(dosym_lines)}
255 ebuild = template.format(**variables)
256 self.ebuild_file.Write(ebuild)
257
258 def UpdateEbuildFile(self, remote, regenerate=False):
259 self.UpdateBinarySymlinks(remote)
260
261 dosym_lines = []
262 for firmware in self.binaries.values():
263 line = "dosym \"%s\" \"/lib/firmware/%s\""
264 dosym_lines.append(line % (firmware.device_file, firmware.symlink_name))
265
266 if regenerate or not self.ebuild_file.exists:
267 self.GenerateEbuildFile(remote, dosym_lines)
268 else:
269 self.UpdateSrcInstall(remote, dosym_lines)
270
271
Dennis Kempin76f89c72014-05-15 15:53:41 -0700272 def VerifySymlinks(self, remote):
273 valid = True
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700274 for firmware in self.GetExistingBinaries():
275 cmd = "readlink -f /lib/firmware/{}".format(firmware.symlink_name)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700276 target = remote.Execute(cmd)
277
278 if not target:
279 msg = "Symlink for firmware '{}' does not exist on device"
280 print msg.format(firmware)
281 valid = False
282
283 target = Path(target)
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700284 if target.basename != firmware.device_file.basename:
Dennis Kempin76f89c72014-05-15 15:53:41 -0700285 msg = "Symlink for firmware '{}' does not point to the right file"
286 print msg.format(firmware)
287 valid = False
288
289 cmd = "ls {}".format(firmware.device_file)
290 if remote.Execute(cmd) is False:
291 msg = "Firmware file {} does not exist on device"
292 print msg.format(firmware.device_file)
293 valid = False
294
295 return valid
296
297 def VerifyFirmwareVersions(self, remote):
298 device_info = remote.GetDeviceInfo(refresh=True)
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700299 binaries = dict([(b.hw_version, b) for b in self.GetExistingBinaries()])
Dennis Kempin76f89c72014-05-15 15:53:41 -0700300
301 valid = True
302 for device in device_info.touch_devices.values():
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700303 if device.hw_version not in binaries:
Dennis Kempin76f89c72014-05-15 15:53:41 -0700304 continue
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700305 firmware = binaries[device.hw_version]
Dennis Kempin76f89c72014-05-15 15:53:41 -0700306 if firmware.fw_version != device.fw_version:
307 print "Device {} did not update correctly:".format(device.hw_version)
308 print "Device version {} != firmware version {}".format(
309 device.fw_version, firmware.fw_version)
310 valid = False
311 return valid
312
313 def __str__(self):
314 res = " firmwares:\n"
315 for firmware in self.firmwares.values():
316 res += " {}".format(firmware)
317 return res