Skip to content

Instantly share code, notes, and snippets.

@jcormier
Last active September 16, 2024 15:50
Show Gist options
  • Select an option

  • Save jcormier/4af3e1a33cfc4d20690350cfa9e134a1 to your computer and use it in GitHub Desktop.

Select an option

Save jcormier/4af3e1a33cfc4d20690350cfa9e134a1 to your computer and use it in GitHub Desktop.
Compare two uncompiled device tree files and output a unified diff ignoring phandle changes
#! /bin/bash
# SPDX-License-Identifier: GPL-2.0-or-later
# This script uses the bash <(...) extension.
# If you want to change this to work with a generic /bin/sh, make sure
# you fix that.
# Change to script dir
cd $(dirname $0)
DTC=dtc
source_and_sort () {
DT="$1"
if [ -d "$DT" ]; then
IFORMAT=fs
elif [ -f "$DT" ]; then
case "$DT" in
*.dts)
IFORMAT=dts
;;
*.dtb)
IFORMAT=dtb
;;
esac
fi
if [ -z "$IFORMAT" ]; then
echo "Unrecognized format for $DT" >&2
exit 2
fi
$DTC -I $IFORMAT -O dts -qq -f -s -o - "$DT"
}
if [ $# != 2 ]; then
echo "Usage: dtdiff <device tree> <device tree>" >&2
exit 1
fi
#diff -u <(source_and_sort "$1") <(source_and_sort "$2")
./dtdiff.py <(source_and_sort "$1") <(source_and_sort "$2")
#!/bin/env python3
"""
Compare two uncompiled device tree files and output a unified diff ignoring phandle changes
Creates a map of phandles to node paths in each file
Compares the two files and finds phandle changes, replacing each phandle with the node path
Outputs a unified diff of the two files with phandle changes applied
Note: Its possible a non-phandle value changes in a way that exactly matches a phandle change.
This script assumes that is very unlikely.
Ex:
`'mii-rt = <0xeb>' --> 'mii-rt = <0xe8>'` is a phandle change
- if 0xeb is in from_phandle_dict
- if 0xe8 is in to_phandle_dict
- the paths are the same
Ex:
`'prus = <0xe8 0xe9>;' --> 'prus = <0xe5 0xe6>;'`
Has two phandle changes. In this case, 0xe8 -> 0xe5 and 0xe9 -> 0xe6
"""
import difflib
import sys
import pprint
import re
def create_phandle_dict(lines):
"""
Create a dictionary of phandles from device tree lines
"""
capture_phandle = re.compile(r'phandle = <(0x[0-9a-f]+)>;')
phandle_dict = {}
current_path = []
for line in lines:
# Look for path lines
if '{' in line:
node_name = line.split()[0]
current_path.append(node_name)
if '}' in line:
current_path.pop()
# Look for phandle lines
m = capture_phandle.search(line)
if m:
phandle = m.group(1)
node_path = "/".join(current_path).replace("//", "/")
phandle_dict[phandle] = "&{" + node_path + "}"
return phandle_dict
def is_phandle_change(a_byte, b_byte, from_phandle_dict, to_phandle_dict):
"""
Return true if diff between two lines is a phandle change
"""
return (
a_byte != b_byte
and a_byte in from_phandle_dict
and b_byte in to_phandle_dict
and from_phandle_dict[a_byte] == to_phandle_dict[b_byte]
)
def get_phandle_changes_for_line(from_lines, to_lines, a_line_num, b_line_num, from_phandle_dict, to_phandle_dict):
"""
Return a list of phandle changes for a single line
A line may have multiple phandle changes:
Ex: `'prus = <0xe8 0xe9>;' --> 'prus = <0xe5 0xe6>;'`
Return: List of changes to make to a_line and b_line:
((from_line_number, phandle, node_name), (to_line_number, phandle, node_name))
"""
phandle_changes = []
a_line = from_lines[a_line_num].strip()
b_line = to_lines[b_line_num].strip()
# print(f"{tag:7} a[{a_line_num}] --> b[{b_line_num}] {a_line!r:>8} --> {b_line!r}")
# Get list of hex values from each line
byte_regex = re.compile(r'0x[0-9a-f]+')
a_bytes = byte_regex.findall(a_line)
b_bytes = byte_regex.findall(b_line)
# Compare each corresponding byte to see if it's a phandle change
for a_byte, b_byte in zip(a_bytes, b_bytes):
if is_phandle_change(a_byte, b_byte, from_phandle_dict, to_phandle_dict):
phandle_node = from_phandle_dict[a_byte]
# print(f"Phandle change: {a_byte} -> {b_byte} in node {phandle_node}")
change = ((a_line_num, a_byte, phandle_node), (b_line_num, b_byte, phandle_node))
phandle_changes.append(change)
return phandle_changes
def get_phandle_changes(from_lines, to_lines, from_phandle_dict, to_phandle_dict):
"""
Create a list of phandle changes
Return: List of changes to make to from_lines and to_lines:
((from_line_number, phandle, node_name), (to_line_number, phandle, node_name))
"""
phandle_changes = []
s = difflib.SequenceMatcher(None, from_lines, to_lines)
# Maps lines to diff tags: 'equal', 'delete', 'insert', 'replace'
for tag, i1, i2, j1, j2 in s.get_opcodes():
# Only handle replace operations
if tag != 'replace':
continue
# Handle differences one line at a time
# TODO Handle changes in file a that don't exactly line up with changes in file b
# Ex:
# - mboxes = <0x9b 0xd8>;
# - timers = <0xd9>;
# - watchdog-timers = <0xda>; <--- This line is missing in the other file
# - memory-region = <0xdb>;
# + mboxes = <0x9b 0xd6>;
# + timers = <0xd7>;
# + memory-region = <0xd8>;
for a_line_num, b_line_num in zip(range(i1, i2), range(j1, j2)):
temp = get_phandle_changes_for_line(from_lines, to_lines, a_line_num, b_line_num, from_phandle_dict, to_phandle_dict)
phandle_changes.extend(temp)
return phandle_changes
def apply_phandle_changes(from_lines, to_lines, phandle_changes):
""" Apply list of phandle changes to from_lines and to_lines """
def apply_change(lines, change):
""" Apply a phandle change to a list of lines """
line_num, byte, phandle_node = change
# print(f"Changing {lines[line_num]!r} phandle {byte} to {phandle_node}")
lines[line_num] = lines[line_num].replace(byte, phandle_node)
for change in phandle_changes:
from_change, to_change = change
apply_change(from_lines, from_change)
apply_change(to_lines, to_change)
def main():
# Check for 2 arguments
if len(sys.argv) != 3:
sys.stderr.write(f"Usage: {sys.argv[0]} <file1> <file2>\n")
file1 = sys.argv[1]
file2 = sys.argv[2]
with open(file1, encoding='utf-8') as fromf:
from_lines = fromf.readlines()
with open(file2, encoding='utf-8') as tof:
to_lines = tof.readlines()
from_phandle_dict = create_phandle_dict(from_lines)
to_phandle_dict = create_phandle_dict(to_lines)
# pprint.pprint(from_phandle_dict)
# pprint.pprint(to_phandle_dict)
phandle_changes = get_phandle_changes(from_lines, to_lines, from_phandle_dict, to_phandle_dict)
# pprint.pprint(phandle_changes)
apply_phandle_changes(from_lines, to_lines, phandle_changes)
# Output unified diff
diff = difflib.unified_diff(from_lines, to_lines, file1, file2)
sys.stdout.writelines(diff)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment