mirror of
https://github.com/google/pebble.git
synced 2025-06-24 01:56:16 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
151
platform/robert/boot/waftools/binary_header.py
Normal file
151
platform/robert/boot/waftools/binary_header.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import binascii
|
||||
|
||||
import sparse_length_encoding
|
||||
|
||||
from waflib import Task, TaskGen, Utils, Node, Errors
|
||||
|
||||
class binary_header(Task.Task):
|
||||
"""
|
||||
Create a header file containing an array with contents from a binary file.
|
||||
"""
|
||||
|
||||
def run(self):
|
||||
if getattr(self.generator, 'hex', False):
|
||||
# Input file is hexadecimal ASCII characters with whitespace
|
||||
text = self.inputs[0].read(
|
||||
encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
|
||||
# Strip all whitespace so that binascii is happy
|
||||
text = ''.join(text.split())
|
||||
code = binascii.unhexlify(text)
|
||||
else:
|
||||
code = self.inputs[0].read('rb')
|
||||
|
||||
array_name = getattr(self.generator, 'array_name', None)
|
||||
if not array_name:
|
||||
array_name = re.sub(r'[^A-Za-z0-9]', '_', self.inputs[0].name)
|
||||
|
||||
if getattr(self.generator, 'compressed', False):
|
||||
encoded_code = ''.join(sparse_length_encoding.encode(code))
|
||||
# verify that it was encoded correctly
|
||||
if ''.join(sparse_length_encoding.decode(encoded_code)) != code:
|
||||
raise Errors.WafError('encoding error')
|
||||
code = encoded_code
|
||||
|
||||
output = ['#pragma once', '#include <stdint.h>']
|
||||
output += ['static const uint8_t %s[] = {' % array_name]
|
||||
line = []
|
||||
for n, b in enumerate(code):
|
||||
line += ['0x%.2x,' % ord(b)]
|
||||
if n % 16 == 15:
|
||||
output += [''.join(line)]
|
||||
line = []
|
||||
if line:
|
||||
output += [''.join(line)]
|
||||
output += ['};', '']
|
||||
|
||||
self.outputs[0].write(
|
||||
'\n'.join(output),
|
||||
encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
|
||||
self.generator.bld.raw_deps[self.uid()] = self.dep_vars = 'array_name'
|
||||
|
||||
if getattr(self.generator, 'chmod', None):
|
||||
os.chmod(self.outputs[0].abspath(), self.generator.chmod)
|
||||
|
||||
|
||||
@TaskGen.feature('binary_header')
|
||||
@TaskGen.before_method('process_source', 'process_rule')
|
||||
def process_binary_header(self):
|
||||
"""
|
||||
Define a transformation that substitutes the contents of *source* files to
|
||||
*target* files::
|
||||
|
||||
def build(bld):
|
||||
bld(
|
||||
features='binary_header',
|
||||
source='foo.bin',
|
||||
target='foo.auto.h',
|
||||
array_name='s_some_array',
|
||||
compressed=True
|
||||
)
|
||||
bld(
|
||||
features='binary_header',
|
||||
source='bar.hex',
|
||||
target='bar.auto.h',
|
||||
hex=True
|
||||
)
|
||||
|
||||
If the *hex* parameter is True, the *source* files are read in an ASCII
|
||||
hexadecimal format, where each byte is represented by a pair of hexadecimal
|
||||
digits with optional whitespace. If *hex* is False or not specified, the
|
||||
file is treated as a raw binary file.
|
||||
|
||||
If the *compressed* parameter is True, the *source* files are compressed with
|
||||
sparse length encoding (see waftools/sparse_length_encoding.py).
|
||||
|
||||
The name of the array variable defaults to the source file name with all
|
||||
characters that are invaid C identifiers replaced with underscores. The name
|
||||
can be explicitly specified by setting the *array_name* parameter.
|
||||
|
||||
This method overrides the processing by
|
||||
:py:meth:`waflib.TaskGen.process_source`.
|
||||
"""
|
||||
|
||||
src = Utils.to_list(getattr(self, 'source', []))
|
||||
if isinstance(src, Node.Node):
|
||||
src = [src]
|
||||
tgt = Utils.to_list(getattr(self, 'target', []))
|
||||
if isinstance(tgt, Node.Node):
|
||||
tgt = [tgt]
|
||||
if len(src) != len(tgt):
|
||||
raise Errors.WafError('invalid number of source/target for %r' % self)
|
||||
|
||||
for x, y in zip(src, tgt):
|
||||
if not x or not y:
|
||||
raise Errors.WafError('null source or target for %r' % self)
|
||||
a, b = None, None
|
||||
|
||||
if isinstance(x, str) and isinstance(y, str) and x == y:
|
||||
a = self.path.find_node(x)
|
||||
b = self.path.get_bld().make_node(y)
|
||||
if not os.path.isfile(b.abspath()):
|
||||
b.sig = None
|
||||
b.parent.mkdir()
|
||||
else:
|
||||
if isinstance(x, str):
|
||||
a = self.path.find_resource(x)
|
||||
elif isinstance(x, Node.Node):
|
||||
a = x
|
||||
if isinstance(y, str):
|
||||
b = self.path.find_or_declare(y)
|
||||
elif isinstance(y, Node.Node):
|
||||
b = y
|
||||
|
||||
if not a:
|
||||
raise Errors.WafError('could not find %r for %r' % (x, self))
|
||||
|
||||
has_constraints = False
|
||||
tsk = self.create_task('binary_header', a, b)
|
||||
for k in ('after', 'before', 'ext_in', 'ext_out'):
|
||||
val = getattr(self, k, None)
|
||||
if val:
|
||||
has_constraints = True
|
||||
setattr(tsk, k, val)
|
||||
|
||||
tsk.before = [k for k in ('c', 'cxx') if k in Task.classes]
|
||||
|
||||
self.source = []
|
||||
|
48
platform/robert/boot/waftools/gitinfo.py
Normal file
48
platform/robert/boot/waftools/gitinfo.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re
|
||||
|
||||
import waflib.Context
|
||||
import waflib.Logs
|
||||
|
||||
|
||||
def get_git_revision(ctx):
|
||||
try:
|
||||
tag = ctx.cmd_and_log(['git', 'describe'], quiet=waflib.Context.BOTH).strip()
|
||||
commit = ctx.cmd_and_log(['git', 'rev-parse', '--short', 'HEAD'],
|
||||
quiet=waflib.Context.BOTH).strip()
|
||||
timestamp = ctx.cmd_and_log(['git', 'log', '-1', '--format=%ct', 'HEAD'],
|
||||
quiet=waflib.Context.BOTH).strip()
|
||||
except Exception:
|
||||
waflib.Logs.warn('get_git_version: unable to determine git revision')
|
||||
tag, commit, timestamp = ("?", "?", "1")
|
||||
# Validate that git tag follows the required form:
|
||||
# See https://github.com/pebble/tintin/wiki/Firmware,-PRF-&-Bootloader-Versions
|
||||
# Note: version_regex.groups() returns sequence ('0', '0', '0', 'suffix'):
|
||||
version_regex = re.search("^v(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:(?:-)(.+))?$", tag)
|
||||
if version_regex:
|
||||
# Get version numbers from version_regex.groups() sequence and replace None values with 0
|
||||
# e.g. v2-beta11 => ('2', None, None, 'beta11') => ('2', '0', '0')
|
||||
version = [x if x else '0' for x in version_regex.groups()[:3]]
|
||||
else:
|
||||
waflib.Logs.warn('get_git_revision: Invalid git tag! '
|
||||
'Must follow this form: `v0[.0[.0]][-suffix]`')
|
||||
version = ['0', '0', '0', 'unknown']
|
||||
return {'TAG': tag,
|
||||
'COMMIT': commit,
|
||||
'TIMESTAMP': timestamp,
|
||||
'MAJOR_VERSION': version[0],
|
||||
'MINOR_VERSION': version[1],
|
||||
'PATCH_VERSION': version[2]}
|
29
platform/robert/boot/waftools/ldscript.py
Normal file
29
platform/robert/boot/waftools/ldscript.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from waflib import Utils, Errors
|
||||
from waflib.TaskGen import after, feature
|
||||
|
||||
|
||||
@after('apply_link')
|
||||
@feature('cprogram', 'cshlib')
|
||||
def process_ldscript(self):
|
||||
if not getattr(self, 'ldscript', None) or self.env.CC_NAME != 'gcc':
|
||||
return
|
||||
|
||||
node = self.path.find_resource(self.ldscript)
|
||||
if not node:
|
||||
raise Errors.WafError('could not find %r' % self.ldscript)
|
||||
self.link_task.env.append_value('LINKFLAGS', '-T%s' % node.abspath())
|
||||
self.link_task.dep_nodes.append(node)
|
84
platform/robert/boot/waftools/objcopy.py
Normal file
84
platform/robert/boot/waftools/objcopy.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
#!/usr/bin/python
|
||||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Grygoriy Fuchedzhy 2010
|
||||
|
||||
"""
|
||||
Support for converting linked targets to ihex, srec or binary files using
|
||||
objcopy. Use the 'objcopy' feature in conjuction with the 'cc' or 'cxx'
|
||||
feature. The 'objcopy' feature uses the following attributes:
|
||||
|
||||
objcopy_bfdname Target object format name (eg. ihex, srec, binary).
|
||||
Defaults to ihex.
|
||||
objcopy_target File name used for objcopy output. This defaults to the
|
||||
target name with objcopy_bfdname as extension.
|
||||
objcopy_install_path Install path for objcopy_target file. Defaults to ${PREFIX}/fw.
|
||||
objcopy_flags Additional flags passed to objcopy.
|
||||
"""
|
||||
|
||||
from waflib.Utils import def_attrs
|
||||
from waflib import Task
|
||||
from waflib.TaskGen import feature, after_method
|
||||
|
||||
|
||||
class objcopy(Task.Task):
|
||||
run_str = '${OBJCOPY} -O ${TARGET_BFDNAME} ${OBJCOPYFLAGS} ${SRC} ${TGT}'
|
||||
color = 'CYAN'
|
||||
|
||||
|
||||
@feature('objcopy')
|
||||
@after_method('apply_link')
|
||||
def objcopy(self):
|
||||
def_attrs(self,
|
||||
objcopy_bfdname='ihex',
|
||||
objcopy_target=None,
|
||||
objcopy_install_path="${PREFIX}/firmware",
|
||||
objcopy_flags='')
|
||||
|
||||
link_output = self.link_task.outputs[0]
|
||||
|
||||
if not self.objcopy_target:
|
||||
self.objcopy_target = link_output.change_ext('.' + self.objcopy_bfdname).name
|
||||
elif isinstance(self.objcopy_target, str):
|
||||
self.objcopy_target = self.path.find_or_declare(self.objcopy_target)
|
||||
|
||||
task = self.create_task('objcopy',
|
||||
src=link_output,
|
||||
tgt=self.objcopy_target)
|
||||
|
||||
task.env.append_unique('TARGET_BFDNAME', self.objcopy_bfdname)
|
||||
try:
|
||||
task.env.append_unique('OBJCOPYFLAGS', getattr(self, 'objcopy_flags'))
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if self.objcopy_install_path:
|
||||
self.bld.install_files(self.objcopy_install_path,
|
||||
task.outputs[0],
|
||||
env=task.env.derive())
|
||||
|
||||
|
||||
def configure(ctx):
|
||||
objcopy = ctx.find_program('objcopy', var='OBJCOPY', mandatory=True)
|
||||
|
||||
|
||||
def objcopy_simple(task, mode):
|
||||
return task.exec_command('arm-none-eabi-objcopy -S -R .stack -R .priv_bss'
|
||||
' -R .bss -O %s "%s" "%s"' %
|
||||
(mode, task.inputs[0].abspath(), task.outputs[0].abspath()))
|
||||
|
||||
|
||||
def objcopy_simple_bin(task):
|
||||
return objcopy_simple(task, 'binary')
|
184
platform/robert/boot/waftools/sparse_length_encoding.py
Executable file
184
platform/robert/boot/waftools/sparse_length_encoding.py
Executable file
|
@ -0,0 +1,184 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
"""
|
||||
Sparse Length Encoding
|
||||
|
||||
A variant of run-length encoding which is tuned specifically to encode binary
|
||||
data with long runs of zeroes interspersed with random (poorly-compressible)
|
||||
data.
|
||||
|
||||
The format is fairly simple. The encoded data is a stream of octets (bytes)
|
||||
beginning with a one-octet header. This header octet is the 'escape byte' that
|
||||
indicates to the decoder that it and the following octets should be treated
|
||||
specially. The encoder selects this escape byte to be an octet which occurs
|
||||
least frequently (or not at all) in the decoded data.
|
||||
|
||||
The following octets of the encoded data are emitted literally until an escape
|
||||
byte is encountered. The escape byte marks the start of an 'escape sequence'
|
||||
comprised of the escape byte itself and one or two following bytes.
|
||||
|
||||
- The escape byte followed by 0x00 indicates the end of input.
|
||||
- The escape byte followed by 0x01 means 'emit a literal escape byte'
|
||||
- The escape byte followed by a byte "b" between 0x02 and 0x7f inclusive means
|
||||
'emit b zeroes'. This two-byte sequence can encode a run of length 2-127.
|
||||
- The escape byte followed by a byte "b" equal to or greater than 0x80
|
||||
(i.e. with the MSB set) means 'take the next byte "c" and emit
|
||||
((b & 0x7f) << 8 | c)+0x80 zeroes'. This three-byte sequence can encode a run
|
||||
of length 128-32895.
|
||||
|
||||
The minimum overhead for this encoding scheme is three bytes: header and
|
||||
end-of-input escape sequence.
|
||||
"""
|
||||
|
||||
from collections import Counter
|
||||
from itertools import groupby
|
||||
|
||||
_MAX_COUNT = 0x807F # max is ((0x7F << 8) | (0xFF) + 0x80
|
||||
|
||||
|
||||
def encode(source):
|
||||
# Analyze the source data to select the escape byte. To keep things simple, we don't allow 0 to
|
||||
# be the escape character.
|
||||
source = bytes(source)
|
||||
frequency = Counter({chr(n): 0 for n in range(1, 256)})
|
||||
frequency.update(source)
|
||||
# most_common() doesn't define what happens if there's a tie in frequency. Let's always pick
|
||||
# the lowest value of that frequency to make the encoding predictable.
|
||||
occurences = frequency.most_common()
|
||||
escape = min(x[0] for x in occurences if x[1] == occurences[-1][1])
|
||||
yield escape
|
||||
for b, g in groupby(source):
|
||||
if b == b'\0':
|
||||
# this is a run of zeros
|
||||
count = len(list(g))
|
||||
while count >= 0x80:
|
||||
# encode the number of zeros using two bytes
|
||||
unit = min(count, _MAX_COUNT)
|
||||
count -= unit
|
||||
unit -= 0x80
|
||||
yield escape
|
||||
yield chr(((unit >> 8) & 0x7F) | 0x80)
|
||||
yield chr(unit & 0xFF)
|
||||
if count == 1:
|
||||
# can't encode a length of 1 zero, so just emit it directly
|
||||
yield b
|
||||
elif 1 < count < 0x80:
|
||||
# encode the number of zeros using one byte
|
||||
yield escape
|
||||
yield chr(count)
|
||||
elif count < 0:
|
||||
raise Exception('Encoding malfunctioned')
|
||||
else:
|
||||
# simply insert the characters (and escape the escape character)
|
||||
for _ in g:
|
||||
yield b
|
||||
if b == escape:
|
||||
yield b'\1'
|
||||
yield escape
|
||||
yield b'\0'
|
||||
|
||||
|
||||
def decode(stream):
|
||||
stream = iter(stream)
|
||||
escape = next(stream)
|
||||
while True:
|
||||
char = next(stream)
|
||||
if char == escape:
|
||||
code = next(stream)
|
||||
if code == b'\0':
|
||||
return
|
||||
elif code == b'\1':
|
||||
yield escape
|
||||
else:
|
||||
if ord(code) & 0x80 == 0:
|
||||
count = ord(code)
|
||||
else:
|
||||
count = (((ord(code) & 0x7f) << 8) | ord(next(stream))) + 0x80
|
||||
assert(count <= _MAX_COUNT)
|
||||
for _ in xrange(count):
|
||||
yield b'\0'
|
||||
else:
|
||||
yield char
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
if len(sys.argv) == 1:
|
||||
# run unit tests
|
||||
import unittest
|
||||
|
||||
class TestSparseLengthEncoding(unittest.TestCase):
|
||||
def test_empty(self):
|
||||
raw_data = ''
|
||||
encoded_data = ''.join(encode(raw_data))
|
||||
decoded_data = ''.join(decode(encoded_data))
|
||||
self.assertEquals(encoded_data, '\x01\x01\x00')
|
||||
|
||||
def test_no_zeros(self):
|
||||
raw_data = '\x02\xff\xef\x99'
|
||||
encoded_data = ''.join(encode(raw_data))
|
||||
decoded_data = ''.join(decode(encoded_data))
|
||||
self.assertEquals(encoded_data, '\x01\x02\xff\xef\x99\x01\x00')
|
||||
|
||||
def test_one_zero(self):
|
||||
raw_data = '\x00'
|
||||
encoded_data = ''.join(encode(raw_data))
|
||||
decoded_data = ''.join(decode(encoded_data))
|
||||
self.assertEquals(encoded_data, '\x01\x00\x01\x00')
|
||||
|
||||
def test_small_number_of_zeros(self):
|
||||
# under 0x80 zeros
|
||||
raw_data = '\0' * 0x0040
|
||||
encoded_data = ''.join(encode(raw_data))
|
||||
decoded_data = ''.join(decode(encoded_data))
|
||||
self.assertEquals(encoded_data, '\x01\x01\x40\x01\x00')
|
||||
self.assertEquals(decoded_data, raw_data)
|
||||
|
||||
def test_medium_number_of_zeros(self):
|
||||
# between 0x80 and 0x807f zeros
|
||||
raw_data = '\0' * 0x1800
|
||||
encoded_data = ''.join(encode(raw_data))
|
||||
decoded_data = ''.join(decode(encoded_data))
|
||||
self.assertEquals(encoded_data, '\x01\x01\x97\x80\x01\x00')
|
||||
self.assertEquals(decoded_data, raw_data)
|
||||
|
||||
def test_remainder_one(self):
|
||||
# leaves a remainder of 1 zero
|
||||
raw_data = '\0' * (0x807f + 1)
|
||||
encoded_data = ''.join(encode(raw_data))
|
||||
decoded_data = ''.join(decode(encoded_data))
|
||||
self.assertEquals(encoded_data, '\x01\x01\xff\xff\x00\x01\x00')
|
||||
self.assertEquals(decoded_data, raw_data)
|
||||
|
||||
def test_remainder_under_128(self):
|
||||
# leaves a remainder of 100 zeros
|
||||
raw_data = '\0' * (0x807f + 100)
|
||||
encoded_data = ''.join(encode(raw_data))
|
||||
decoded_data = ''.join(decode(encoded_data))
|
||||
self.assertEquals(encoded_data, '\x01\x01\xff\xff\x01\x64\x01\x00')
|
||||
self.assertEquals(decoded_data, raw_data)
|
||||
|
||||
unittest.main()
|
||||
elif len(sys.argv) == 2:
|
||||
# encode the specified file
|
||||
data = open(sys.argv[1], 'rb').read()
|
||||
encoded = ''.join(encode(data))
|
||||
if ''.join(decode(encoded)) != data:
|
||||
raise Exception('Invalid encoding')
|
||||
sys.stdout.write(''.join(encode(f)))
|
||||
else:
|
||||
raise Exception('Invalid arguments')
|
Loading…
Add table
Add a link
Reference in a new issue