blob: d8adc2e1b0698339d955119f83b6abe69dbe63d8 [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
Charlie Mooneyf762ef22014-08-12 10:53:12 -070096 self.bcsname = "bcs-{}-private".format(variant if variant else board)
Dennis Kempin76f89c72014-05-15 15:53:41 -070097
Charlie Mooneyf762ef22014-08-12 10:53:12 -070098 self.ebuild_name = "chromeos-touch-firmware-{}".format(
99 variant if variant else board)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700100 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 Kempin6fd10e52014-05-29 15:49:43 -0700108 self.bcs_url = None
109 self.ebuild_version = None
Dennis Kempin76f89c72014-05-15 15:53:41 -0700110
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700111 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 Kempin76f89c72014-05-15 15:53:41 -0700115
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700116 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 Kempin76f89c72014-05-15 15:53:41 -0700122
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700123 def _UpdateVersion(self, version):
124 self.ebuild_version = version
125 self.ebuild_symlink = self.ebuild_dir / "{}-{}.ebuild".format(
126 self.ebuild_name, version)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700127 url = "gs://chromeos-binaries/HOME/{}/{}/chromeos-base/{}/{}-{}.tbz2"
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700128 self.bcs_url = url.format(self.bcsname, self.overlay.basename,
Dennis Kempin76f89c72014-05-15 15:53:41 -0700129 self.ebuild_name, self.ebuild_name,
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700130 version)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700131
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 Kempin6fd10e52014-05-29 15:49:43 -0700143 if not self.ebuild_version:
144 return
145
Dennis Kempin76f89c72014-05-15 15:53:41 -0700146 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 Mooneyf762ef22014-08-12 10:53:12 -0700166 tar_name = "{}-{}".format(self.ebuild_name, version)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700167 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 Kempin6fd10e52014-05-29 15:49:43 -0700189 if old_symlink and old_symlink != new_symlink:
Dennis Kempin76f89c72014-05-15 15:53:41 -0700190 self.ebuild_repo.Move(old_symlink, new_symlink)
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700191 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 Kempin76f89c72014-05-15 15:53:41 -0700195
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 Kempin6fd10e52014-05-29 15:49:43 -0700212 def UpdateSrcInstall(self, remote, dosym_lines):
Dennis Kempin76f89c72014-05-15 15:53:41 -0700213 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 Kempin76f89c72014-05-15 15:53:41 -0700234 out.write("\tcros-binary_src_install\n\n")
235
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700236 for line in dosym_lines:
237 out.write("\t{}\n".format(line))
Dennis Kempin76f89c72014-05-15 15:53:41 -0700238
239 out.write("}\n")
240 out.write(ebuild[end:].strip())
241 out.close()
242
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700243 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 Kempin76f89c72014-05-15 15:53:41 -0700273 def VerifySymlinks(self, remote):
274 valid = True
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700275 for firmware in self.GetExistingBinaries():
276 cmd = "readlink -f /lib/firmware/{}".format(firmware.symlink_name)
Dennis Kempin76f89c72014-05-15 15:53:41 -0700277 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 Kempin6fd10e52014-05-29 15:49:43 -0700285 if target.basename != firmware.device_file.basename:
Dennis Kempin76f89c72014-05-15 15:53:41 -0700286 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 Kempin6fd10e52014-05-29 15:49:43 -0700300 binaries = dict([(b.hw_version, b) for b in self.GetExistingBinaries()])
Dennis Kempin76f89c72014-05-15 15:53:41 -0700301
302 valid = True
303 for device in device_info.touch_devices.values():
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700304 if device.hw_version not in binaries:
Dennis Kempin76f89c72014-05-15 15:53:41 -0700305 continue
Dennis Kempin6fd10e52014-05-29 15:49:43 -0700306 firmware = binaries[device.hw_version]
Dennis Kempin76f89c72014-05-15 15:53:41 -0700307 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