1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0-only 3# 4# Check DTS coding style on YAML binding examples and on 5# .dts/.dtsi/.dtso source files. Enforces rules from 6# Documentation/devicetree/bindings/dts-coding-style.rst. 7# 8# Two modes: 9# --mode=relaxed (default) 10# Only rules that produce zero warnings on the current tree. 11# Suitable for dt_binding_check. 12# --mode=strict 13# All rules. Required for new submissions. 14# 15# Two input types (auto-detected by file extension): 16# *.yaml -- DT binding; check each example block 17# *.dts/*.dtsi/*.dtso -- DTS source; whole file is one block 18# 19# Rules are declared in a registry (see RULES below); each rule is 20# tagged with the lowest mode that runs it. Promoting a rule from 21# 'strict' to 'relaxed' is a one-line change. 22 23import argparse 24import re 25import sys 26from enum import Enum, auto 27 28import ruamel.yaml 29 30 31# --------------------------------------------------------------------------- 32# Line classification 33# --------------------------------------------------------------------------- 34 35class LineType(Enum): 36 BLANK = auto() 37 COMMENT = auto() # // ... or /* ... */ on one line 38 COMMENT_START = auto() # /* without closing */ 39 COMMENT_BODY = auto() # inside a multi-line comment 40 COMMENT_END = auto() # closing */ 41 PREPROCESSOR = auto() # #include / #define / #ifdef / ... 42 NODE_OPEN = auto() # something { (with optional label/name/addr) 43 NODE_CLOSE = auto() # }; 44 PROPERTY = auto() # name = value; or name; 45 CONTINUATION = auto() # continuation of a multi-line property 46 47 48re_cpp_directive = re.compile( 49 r'^#\s*(include|define|undef|ifdef|ifndef|if|else|elif|endif|' 50 r'pragma|error|warning)\b') 51 52re_dtc_directive = re.compile( 53 r'^/(dts-v1|include)/') 54 55# label: name@addr { -- label and addr optional; name can be "/" 56# Per the DT spec a node name may start with a digit (e.g. 1wire@...). 57# The address part is captured loosely (any non-space, non-brace run) so 58# malformed addresses (e.g. memory@0x1000) still reach 59# check_unit_address_format() instead of silently bypassing the check. 60re_node_header = re.compile( 61 r'^(?:([a-zA-Z_][a-zA-Z0-9_]*):\s*)?' 62 r'([a-zA-Z0-9][a-zA-Z0-9,._+-]*|/)' 63 r'(?:@([^\s{]+))?' 64 r'\s*\{$') 65 66re_ref_node = re.compile( 67 r'^&([a-zA-Z_][a-zA-Z0-9_]*)\s*\{$') 68 69 70def is_preprocessor(stripped): 71 """Tell C preprocessor directives apart from DTS '#'-prefixed props.""" 72 if re_cpp_directive.match(stripped) is not None: 73 return True 74 if re_dtc_directive.match(stripped) is not None: 75 return True 76 return False 77 78 79class DtsLine: 80 __slots__ = ('lineno', 'raw', 'linetype', 'indent_str', 'stripped', 81 'prop_name', 'continuations', 82 'node_name', 'node_addr', 'label', 'ref_name', 'depth', 83 'closures') 84 85 def __init__(self, lineno, raw, linetype, depth, indent_str, stripped): 86 self.lineno = lineno # 1-based within the block 87 self.raw = raw 88 self.linetype = linetype 89 self.indent_str = indent_str # leading whitespace as-is 90 self.depth = depth 91 self.stripped = stripped 92 self.prop_name = None 93 self.continuations = [] 94 self.node_name = None 95 self.node_addr = None 96 self.label = None 97 self.ref_name = None 98 self.closures = 1 # count of '}' on a NODE_CLOSE line 99 100 101def _split_code(text): 102 """Return (code, opens_block) for a leading-stripped line: the 103 code portion with // and /* */ comments removed (string literals 104 kept verbatim), and whether a /* */ block comment is left open. 105 The code portion is right-stripped so the endswith() checks in 106 classify_lines see code only, not a trailing comment or blanks.""" 107 out = [] 108 i = 0 109 n = len(text) 110 while i < n: 111 c = text[i] 112 if c == '"': 113 j = i + 1 114 while j < n: 115 if text[j] == '\\': 116 j += 2 117 continue 118 if text[j] == '"': 119 j += 1 120 break 121 j += 1 122 out.append(text[i:j]) 123 i = j 124 continue 125 if c == '/' and i + 1 < n and text[i + 1] == '/': 126 break 127 if c == '/' and i + 1 < n and text[i + 1] == '*': 128 end = text.find('*/', i + 2) 129 if end < 0: 130 return (''.join(out).rstrip(), True) 131 i = end + 2 132 continue 133 out.append(c) 134 i += 1 135 return (''.join(out).rstrip(), False) 136 137 138re_only_closures = re.compile(r'(?:\}\s*;?\s*)+$') 139 140 141def classify_lines(text): 142 """Return a list of DtsLine. Tracks { } depth and groups 143 continuation lines onto their leading PROPERTY line.""" 144 out = [] 145 in_block_comment = False 146 in_cpp_macro = False 147 prev_complete = True 148 depth = 0 149 150 # Split preserving the indent string verbatim 151 re_lead = re.compile(r'^([ \t]*)(.*)$') 152 153 for i, raw in enumerate(text.split('\n'), start=1): 154 m = re_lead.match(raw) 155 indent_str = m.group(1) 156 stripped = m.group(2) 157 158 # Continuation of a multi-line C preprocessor directive: the 159 # previous PREPROCESSOR line ended with a '\\' line splice, so 160 # this line is part of the same macro. Treat it as 161 # PREPROCESSOR until the splice chain ends (no trailing '\\' 162 # or a blank line). 163 if in_cpp_macro: 164 dl = DtsLine(i, raw, LineType.PREPROCESSOR, 165 depth, indent_str, stripped) 166 out.append(dl) 167 in_cpp_macro = (bool(stripped) and 168 stripped.rstrip().endswith('\\')) 169 continue 170 171 if not stripped: 172 dl = DtsLine(i, raw, LineType.BLANK, depth, '', '') 173 out.append(dl) 174 continue 175 176 if in_block_comment: 177 ltype = (LineType.COMMENT_END if '*/' in stripped 178 else LineType.COMMENT_BODY) 179 if ltype == LineType.COMMENT_END: 180 in_block_comment = False 181 dl = DtsLine(i, raw, ltype, depth, indent_str, stripped) 182 out.append(dl) 183 continue 184 185 if (stripped.startswith('#') or stripped.startswith('/')) and is_preprocessor(stripped): 186 dl = DtsLine(i, raw, LineType.PREPROCESSOR, depth, 187 indent_str, stripped) 188 out.append(dl) 189 prev_complete = True 190 in_cpp_macro = stripped.rstrip().endswith('\\') 191 continue 192 193 # Strip comments first so all later structural checks see code 194 # only. An unclosed /* sets in_block_comment for the next line. 195 code, opens_block = _split_code(stripped) 196 if opens_block: 197 in_block_comment = True 198 199 # Pure-comment line: nothing left after stripping. Classify as 200 # COMMENT_START (carries to next line) or COMMENT, and skip the 201 # structural classification entirely. 202 if not code: 203 ltype = LineType.COMMENT_START if opens_block else LineType.COMMENT 204 dl = DtsLine(i, raw, ltype, depth, indent_str, stripped) 205 out.append(dl) 206 continue 207 208 if not prev_complete: 209 dl = DtsLine(i, raw, LineType.CONTINUATION, depth, indent_str, code) 210 out.append(dl) 211 prev_complete = (code.endswith(';') or 212 code.endswith('{') or 213 code.endswith('};')) 214 continue 215 216 # NODE_CLOSE: the canonical form is "}" or "};" alone. A line 217 # that is nothing but closures (e.g. "}; };") is still treated 218 # as NODE_CLOSE for depth tracking, but the multi-closure case 219 # is flagged separately by check_node_close_alone via 220 # dl.closures. 221 if re_only_closures.match(code): 222 closures = code.count('}') 223 depth = max(depth - closures, 0) 224 dl = DtsLine(i, raw, LineType.NODE_CLOSE, depth, indent_str, code) 225 dl.closures = closures 226 out.append(dl) 227 prev_complete = True 228 continue 229 230 if code.endswith('{'): 231 dl = DtsLine(i, raw, LineType.NODE_OPEN, depth, indent_str, code) 232 parse_node_header(dl) 233 out.append(dl) 234 depth += 1 235 prev_complete = True 236 continue 237 238 # Property (or first line of a multi-line property). 239 dl = DtsLine(i, raw, LineType.PROPERTY, depth, indent_str, code) 240 parse_property_name(dl) 241 out.append(dl) 242 prev_complete = code.endswith(';') 243 244 # Group continuation lines onto their leading PROPERTY. 245 last_prop = None 246 grouped = [] 247 for dl in out: 248 if dl.linetype == LineType.CONTINUATION and last_prop is not None: 249 last_prop.continuations.append(dl) 250 continue 251 if dl.linetype == LineType.PROPERTY: 252 last_prop = dl 253 elif dl.linetype != LineType.BLANK and \ 254 dl.linetype not in (LineType.COMMENT, LineType.COMMENT_BODY, 255 LineType.COMMENT_END, 256 LineType.COMMENT_START): 257 last_prop = None 258 grouped.append(dl) 259 return grouped 260 261 262def parse_node_header(dl): 263 m = re_node_header.match(dl.stripped) 264 if m: 265 dl.label = m.group(1) 266 dl.node_name = m.group(2) 267 dl.node_addr = m.group(3) 268 return 269 m = re_ref_node.match(dl.stripped) 270 if m: 271 dl.ref_name = m.group(1) 272 273 274def parse_property_name(dl): 275 m = re.match(r'^([a-zA-Z0-9#][a-zA-Z0-9,._+#-]*)\s*[=;]', dl.stripped) 276 if m: 277 dl.prop_name = m.group(1) 278 279 280def collect_labels_and_refs(text): 281 """Return (defined_labels, referenced_labels) found anywhere outside 282 /* */ comments and string literals. Labels named fake_intc* (injected 283 by dt-extract-example) are skipped.""" 284 # Strip block comments first so labels inside them don't count 285 stripped = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) 286 # Strip line comments 287 stripped = re.sub(r'//[^\n]*', '', stripped) 288 # Strip string literals so words inside quotes (e.g. "Error: foo") 289 # are not picked up as label definitions or &-references. 290 stripped = re.sub(r'"(?:[^"\\]|\\.)*"', '""', stripped) 291 defined = set() 292 referenced = set() 293 # A label precedes a node header; the next non-space token may start 294 # with a letter (foo, &ref), a digit (1wire), or '/' (root node). 295 for m in re.finditer( 296 r'(?:^|[\s{])([a-zA-Z_][a-zA-Z0-9_]*):\s*[a-zA-Z0-9/&]', 297 stripped): 298 name = m.group(1) 299 if not name.startswith('fake_intc'): 300 defined.add(name) 301 for m in re.finditer(r'&([a-zA-Z_][a-zA-Z0-9_]*)', stripped): 302 referenced.add(m.group(1)) 303 return defined, referenced 304 305 306# --------------------------------------------------------------------------- 307# Rule registry 308# --------------------------------------------------------------------------- 309 310class Ctx: 311 """Context passed to each rule check. Carries the parsed lines, 312 raw text, mode and kind.""" 313 314 def __init__(self, lines, text, mode, kind): 315 self.lines = lines 316 self.text = text 317 self.mode = mode # 'relaxed' or 'strict' 318 if kind in DTS_FAMILY: 319 self.file_type = 'dts' 320 else: 321 self.file_type = 'yaml' 322 323 324class Rule: 325 __slots__ = ('name', 'mode', 'description', 'check', 'applies_to') 326 327 def __init__(self, name, mode, description, check, 328 applies_to=('yaml', 'dts', 'dtsi', 'dtso')): 329 self.name = name 330 self.mode = mode # 'relaxed' or 'strict' 331 self.description = description 332 self.check = check 333 self.applies_to = applies_to # input types this rule covers 334 335 336# --- individual rule check functions -------------------------------------- 337 338def check_trailing_whitespace(ctx): 339 for dl in ctx.lines: 340 if dl.raw != dl.raw.rstrip(): 341 yield (dl.lineno, 'trailing whitespace') 342 343 344def check_tab_in_yaml_example(ctx): 345 """Reject literal tabs in DTS lines when input is YAML. 346 347 For YAML examples, indent and content must use spaces. Tabs inside 348 a #define value are tolerated (those are CPP macros, not DTS). 349 For .dts files, this rule does not apply -- tabs are required. 350 """ 351 if ctx.file_type != 'yaml': 352 return 353 for dl in ctx.lines: 354 if dl.linetype == LineType.PREPROCESSOR: 355 continue 356 if dl.linetype == LineType.BLANK: 357 continue 358 if '\t' in dl.raw: 359 yield (dl.lineno, 'tab character not allowed in DTS example') 360 361 362def check_mixed_indent_chars(ctx): 363 """Indent must be all-tabs, except for aligning indentation (comments 364 or continued lines).""" 365 for dl in ctx.lines: 366 if not dl.indent_str: 367 continue 368 if dl.linetype == LineType.PREPROCESSOR: 369 continue 370 if re.search(r' \t', dl.indent_str): 371 yield (dl.lineno, 'mixed tabs and spaces in indent') 372 if dl.indent_str.count(' ') > 7: 373 yield (dl.lineno, 'too many space characters in indent (more than 7)') 374 for cont in dl.continuations: 375 if not cont.indent_str: 376 continue 377 if cont.linetype == LineType.PREPROCESSOR: 378 continue 379 if re.search(r' \t', cont.indent_str): 380 yield (cont.lineno, 'mixed tabs and spaces in indent') 381 382 383def detect_indent_unit(ctx): 384 """Find the indent unit used at depth 1 in this block. 385 386 Returns tuple of string (one of: ' ' (2 spaces), ' ' (4 spaces), 387 '\\t' (tab), or None if depth-1 is empty or ambiguous) and line number when 388 detection was made).""" 389 for dl in ctx.lines: 390 if dl.depth != 1: 391 continue 392 if dl.linetype in (LineType.BLANK, LineType.PREPROCESSOR): 393 continue 394 if dl.linetype in (LineType.COMMENT_BODY, LineType.COMMENT_END): 395 continue 396 if not dl.indent_str: 397 continue 398 if dl.indent_str == '\t': 399 return ('\t', dl.lineno) 400 if dl.indent_str == ' ': 401 return (' ', dl.lineno) 402 if dl.indent_str == ' ': 403 return (' ', dl.lineno) 404 # Anything else at depth 1 is non-canonical; flag elsewhere. 405 return (dl.indent_str, dl.lineno) 406 return (None, None) 407 408 409def check_indent_unit_relaxed(ctx): 410 """YAML examples: 2 or 4 spaces. Never tabs or other widths.""" 411 (unit, lineno) = detect_indent_unit(ctx) 412 if unit is None: 413 return 414 if unit not in (' ', ' '): 415 yield (lineno, 'indent unit must be 2 or 4 spaces, got %r' % unit) 416 417 418def check_indent_unit_dts(ctx): 419 """DTS files: 1 tab per level. Always required.""" 420 (unit, lineno) = detect_indent_unit(ctx) 421 if unit is None: 422 return 423 if unit != '\t': 424 yield (lineno, 'indent unit must be 1 tab in DTS, got %r' % unit) 425 426 427def check_indent_unit_strict(ctx): 428 """YAML: must be exactly 4 spaces. DTS: 1 tab (same as relaxed).""" 429 (unit, lineno) = detect_indent_unit(ctx) 430 if unit is None: 431 return 432 if ctx.file_type == 'yaml': 433 if unit != ' ': 434 yield (lineno, 'indent unit must be 4 spaces in strict mode, ' 435 'got %r' % unit) 436 437 438def check_indent_consistent(ctx): 439 """All indented lines must be a multiple of the detected unit.""" 440 (unit, lineno) = detect_indent_unit(ctx) 441 if unit is None: 442 return 443 if ctx.file_type == 'yaml': 444 if unit not in (' ', ' '): 445 return # let check_indent_unit_* report this 446 else: 447 if unit != '\t': 448 return 449 450 for dl in ctx.lines: 451 if dl.linetype in (LineType.BLANK, LineType.PREPROCESSOR): 452 continue 453 if dl.linetype == LineType.CONTINUATION: 454 continue # continuations align to <, not to indent unit 455 if dl.linetype in (LineType.COMMENT_BODY, LineType.COMMENT_END): 456 continue 457 if not dl.indent_str: 458 continue 459 # The indent must be 'unit' repeated dl.depth times, exactly. 460 # NODE_CLOSE lines have depth equal to the post-decrement value, 461 # which matches the indent expected. 462 expected = unit * dl.depth 463 if dl.indent_str != expected: 464 yield (dl.lineno, 465 'indent mismatch (expected depth %d * %r)' % 466 (dl.depth, unit)) 467 468 469def check_blank_lines(ctx): 470 """No two consecutive blank lines, no leading/trailing blank lines 471 in any node body.""" 472 lines = ctx.lines 473 # Consecutive blanks 474 for i in range(1, len(lines)): 475 if lines[i].linetype == LineType.BLANK and \ 476 lines[i - 1].linetype == LineType.BLANK: 477 yield (lines[i].lineno, 'consecutive blank lines') 478 # Blank right after { or right before } 479 for i, dl in enumerate(lines): 480 if dl.linetype != LineType.BLANK: 481 continue 482 prev = lines[i - 1] if i > 0 else None 483 nxt = lines[i + 1] if i + 1 < len(lines) else None 484 if prev is not None and prev.linetype == LineType.NODE_OPEN: 485 yield (dl.lineno, 'blank line at start of node body') 486 if nxt is not None and nxt.linetype == LineType.NODE_CLOSE: 487 yield (dl.lineno, 'blank line at end of node body') 488 489 490def _walk_bodies(lines): 491 """Yield lists of immediate-child NODE_OPEN lines for each node body 492 in the input. Skips ref-nodes (&label) since those don't have an 493 intrinsic ordering.""" 494 body_stack = [[]] 495 for dl in lines: 496 if dl.linetype == LineType.NODE_OPEN: 497 body_stack[-1].append(dl) 498 body_stack.append([]) 499 continue 500 if dl.linetype == LineType.NODE_CLOSE: 501 if len(body_stack) <= 1: 502 # Unbalanced; ignore to avoid crashing on malformed input 503 continue 504 yield body_stack.pop() 505 continue 506 while body_stack: 507 yield body_stack.pop() 508 509 510def _natural_sort_key(s): 511 """Split a string into a tuple of (kind, value) pairs that compares 512 numeric runs as ints, so 'foo10' sorts after 'foo2'.""" 513 parts = [] 514 for part in re.split(r'(\d+)', s): 515 if part.isdigit(): 516 parts.append((0, int(part))) 517 else: 518 parts.append((1, part)) 519 return tuple(parts) 520 521 522def check_child_address_order(ctx): 523 """Addressed siblings (foo@N) must appear in ascending address 524 order within their parent node body.""" 525 for children in _walk_bodies(ctx.lines): 526 addressed = [] 527 for c in children: 528 if c.node_addr is None: 529 continue 530 try: 531 parts = tuple(int(p, 16) for p in c.node_addr.split(',')) 532 except ValueError: 533 continue 534 addressed.append((parts, c)) 535 for i in range(1, len(addressed)): 536 if addressed[i][0] < addressed[i - 1][0]: 537 dl = addressed[i][1] 538 yield (dl.lineno, 539 'child node @%s out of address order' % 540 dl.node_addr) 541 542 543def check_child_name_order(ctx): 544 """Unaddressed siblings must appear in natural-sort order by node 545 name within their parent node body. Addressed children are scoped 546 by check_child_address_order; reference nodes (&label { ... }) and 547 the root node are skipped.""" 548 for children in _walk_bodies(ctx.lines): 549 unaddressed = [] 550 for c in children: 551 if c.node_addr is not None: 552 continue 553 if c.node_name in (None, '/'): 554 continue 555 if c.ref_name is not None: 556 continue 557 unaddressed.append((_natural_sort_key(c.node_name), c)) 558 for i in range(1, len(unaddressed)): 559 if unaddressed[i][0] < unaddressed[i - 1][0]: 560 dl = unaddressed[i][1] 561 yield (dl.lineno, 562 'child node %r out of name order' % dl.node_name) 563 564 565def _property_bucket(name): 566 """Return the canonical bucket index for a property: 567 0 device_type 568 1 compatible 569 2 reg / reg-names 570 3 ranges 571 4 standard properties (no vendor comma in #-stripped name) 572 5 vendor-specific properties 573 6 status 574 Plus a sub-key inside the bucket for fixed slots (device_type, compatible, 575 reg, reg-names, ranges, status). 'standard' and 'vendor' return None for 576 the sub-key, signalling that the within-bucket key is computed by 577 the pairing rules.""" 578 stripped = name.lstrip('#') 579 if name == 'device_type': 580 return (0, 0) 581 if name == 'compatible': 582 return (1, 0) 583 if name == 'reg': 584 return (2, 0) 585 if name == 'reg-names': 586 return (2, 1) 587 if name == 'ranges': 588 return (3, 0) 589 if name == 'status': 590 return (6, 0) 591 return (5 if ',' in stripped else 4, None) 592 593 594# Declarative pairing rules: each is a callable 595# (name, all_names) -> anchor_name_or_None 596# If a rule returns an anchor, the property sorts immediately after the 597# anchor. Rules are tried in order; the first match wins. If none 598# matches, the within-bucket key falls back to natural sort by the 599# #-stripped name. 600 601def _pair_pinctrl_names(name, all_names): 602 """pinctrl-names follows the highest pinctrl-N in the same node.""" 603 if name != 'pinctrl-names': 604 return None 605 cands = [n for n in all_names if re.match(r'^pinctrl-\d+$', n)] 606 if not cands: 607 return None 608 return max(cands, key=_natural_sort_key) 609 610 611def _pair_x_names(name, all_names): 612 """Generic <x>-names follows its owning property. The owner is 613 usually plural (clocks/clock-names, dmas/dma-names, 614 resets/reset-names) but occasionally singular (reg/reg-names is 615 handled by the fixed slot above; this rule catches anything else).""" 616 if not name.endswith('-names'): 617 return None 618 base = name[:-len('-names')] 619 # Try plural and singular forms. 620 if (base + 's') in all_names: 621 return base + 's' 622 if base in all_names: 623 return base 624 return None 625 626 627PAIRING_RULES = (_pair_pinctrl_names, _pair_x_names) 628 629 630def _property_sort_key(name, all_names): 631 """Sort key for a property among its node-body siblings. 632 633 Format: (bucket, within_key, tiebreak). 'within_key' for 634 standard/vendor buckets follows pairing rules: a property paired 635 with anchor X sorts as if it were X with a higher tiebreak.""" 636 bucket, fixed_sub = _property_bucket(name) 637 if fixed_sub is not None: 638 return (bucket, (), fixed_sub) 639 640 for rule in PAIRING_RULES: 641 anchor = rule(name, all_names) 642 if anchor is not None: 643 return (bucket, _natural_sort_key(anchor.lstrip('#')), 1) 644 645 return (bucket, _natural_sort_key(name.lstrip('#')), 0) 646 647 648def check_property_order(ctx): 649 """Properties within a node body must appear in canonical order: 650 compatible, reg(/reg-names), ranges, then the standard group, then 651 the vendor-specific group, then status. Inside the standard and 652 vendor groups, pairing rules apply (e.g. <x>-names follows <x>); 653 everything else falls back to natural sort by the #-stripped name.""" 654 lines = ctx.lines 655 for i, dl in enumerate(lines): 656 if dl.linetype != LineType.NODE_OPEN: 657 continue 658 body_depth = dl.depth + 1 659 props = [] 660 for j in range(i + 1, len(lines)): 661 d = lines[j] 662 if d.linetype == LineType.NODE_CLOSE and \ 663 d.depth == body_depth - 1: 664 break 665 if d.linetype == LineType.PROPERTY and d.depth == body_depth \ 666 and d.prop_name is not None: 667 props.append(d) 668 if len(props) < 2: 669 continue 670 all_names = [p.prop_name for p in props] 671 keyed = [(p, _property_sort_key(p.prop_name, all_names)) 672 for p in props] 673 for k in range(1, len(keyed)): 674 if keyed[k][1] < keyed[k - 1][1]: 675 p = keyed[k][0] 676 prev = keyed[k - 1][0] 677 yield (p.lineno, 678 'property %r out of canonical order ' 679 '(should sort before %r)' % 680 (p.prop_name, prev.prop_name)) 681 682 683def _strip_strings_and_comments(text): 684 """Remove string literals and /* */ + // comments from a single 685 line, replacing them with empty strings. Used so syntactic checks 686 (whitespace, hex case, etc.) don't false-positive on contents of 687 quoted strings or comments. An unclosed /* on the line is treated 688 as a comment running to end of line.""" 689 text = re.sub(r'"(?:[^"\\]|\\.)*"', '""', text) 690 text = re.sub(r'/\*.*?\*/', '', text) 691 text = re.sub(r'/\*.*$', '', text) 692 text = re.sub(r'//.*$', '', text) 693 return text 694 695 696def check_required_blank_lines(ctx): 697 """A blank line must precede each child node and the 'status' 698 property within a node body, except when these are the first 699 substantive item in the body.""" 700 lines = ctx.lines 701 for i, open_dl in enumerate(lines): 702 if open_dl.linetype != LineType.NODE_OPEN: 703 continue 704 body_depth = open_dl.depth + 1 705 prev_substantive = None 706 between_blanks = 0 707 depth_inside = 0 708 for j in range(i + 1, len(lines)): 709 d = lines[j] 710 if d.linetype == LineType.NODE_CLOSE and \ 711 d.depth == body_depth - 1 and depth_inside == 0: 712 break 713 # Track depth inside nested children so we only look at 714 # immediate-body items. 715 if d.linetype == LineType.NODE_OPEN and \ 716 d.depth >= body_depth and depth_inside > 0: 717 depth_inside += 1 718 continue 719 if d.linetype == LineType.NODE_CLOSE and depth_inside > 0: 720 depth_inside -= 1 721 continue 722 if depth_inside > 0: 723 continue 724 if d.linetype == LineType.BLANK: 725 if prev_substantive is not None: 726 between_blanks += 1 727 continue 728 if d.linetype in (LineType.COMMENT, LineType.COMMENT_START, 729 LineType.COMMENT_BODY, LineType.COMMENT_END, 730 LineType.PREPROCESSOR): 731 continue 732 if d.linetype == LineType.CONTINUATION: 733 continue 734 735 needs_blank = False 736 if d.linetype == LineType.NODE_OPEN: 737 needs_blank = True 738 depth_inside = 1 # entered the child body 739 elif d.linetype == LineType.PROPERTY and d.prop_name == 'status': 740 needs_blank = True 741 742 if needs_blank and prev_substantive is not None and \ 743 between_blanks == 0: 744 if d.linetype == LineType.NODE_OPEN: 745 yield (d.lineno, 746 'child node must be preceded by a blank line') 747 else: 748 yield (d.lineno, 749 '"status" must be preceded by a blank line') 750 751 prev_substantive = d 752 between_blanks = 0 753 754 755def check_hex_case(ctx): 756 """Hex literals (0xN) must use lowercase digits and prefix.""" 757 for dl in ctx.lines: 758 if dl.linetype in (LineType.BLANK, LineType.COMMENT, 759 LineType.COMMENT_START, LineType.COMMENT_BODY, 760 LineType.COMMENT_END, LineType.PREPROCESSOR): 761 continue 762 text = _strip_strings_and_comments(dl.raw) 763 for m in re.finditer(r'\b0[xX][0-9a-fA-F]+\b', text): 764 lit = m.group(0) 765 if any(c.isupper() for c in lit[2:]) or lit[1] == 'X': 766 yield (dl.lineno, 767 'hex literal %r must be lowercase' % lit) 768 769 770def check_unit_address_format(ctx): 771 """Unit addresses must be lowercase hex without leading zeros and 772 without a '0x' prefix. For multi-cell addresses (comma-separated), 773 each part is checked independently. A single '0' is permitted 774 (canonical zero).""" 775 for dl in ctx.lines: 776 if dl.linetype != LineType.NODE_OPEN: 777 continue 778 if dl.node_addr is None: 779 continue 780 addr = dl.node_addr 781 for part in addr.split(','): 782 if part[:2] in ('0x', '0X'): 783 yield (dl.lineno, 784 'unit address %r must not have a "0x" prefix' % 785 addr) 786 break 787 if not re.match(r'^[0-9a-fA-F]+$', part): 788 yield (dl.lineno, 789 'unit address %r is not valid hex' % addr) 790 break 791 if any(c in 'ABCDEF' for c in part): 792 yield (dl.lineno, 793 'unit address %r must be lowercase hex' % addr) 794 break 795 if len(part) > 1 and part.startswith('0'): 796 yield (dl.lineno, 797 'unit address %r has leading zeros' % addr) 798 break 799 800 801def check_value_whitespace(ctx): 802 """A <...> cell list must have no whitespace directly after '<' 803 or directly before '>'. Continuation lines are joined onto the 804 property so a <...> split across lines is checked too; a '<' or 805 '>' at a line break is glued straight to the neighbouring value, 806 so the break itself is not counted as padding. Outside strings 807 and comments only.""" 808 for dl in ctx.lines: 809 if dl.linetype != LineType.PROPERTY: 810 continue 811 segs = [_strip_strings_and_comments(dl.raw).strip()] 812 for cont in dl.continuations: 813 segs.append(_strip_strings_and_comments(cont.stripped).strip()) 814 text = '' 815 for s in segs: 816 if not s: 817 continue 818 if not text or text.endswith('<') or s.startswith('>'): 819 text += s 820 else: 821 text += ' ' + s 822 for m in re.finditer(r'<([^<>]*)>', text): 823 content = m.group(1) 824 if content and content != content.strip(): 825 yield (dl.lineno, 'extra whitespace inside <...>') 826 break 827 828 829def check_node_close_alone(ctx): 830 """The closing '};' of a node must be on its own line. The 831 classifier accepts a canonical "}" or "};" as NODE_CLOSE; a line 832 that is all closures (e.g. "}; };") is still NODE_CLOSE for depth 833 tracking but is flagged here via dl.closures. Any other line that 834 still contains '};' (in code, not in strings or comments) is 835 mixing a node close with something else.""" 836 for dl in ctx.lines: 837 if dl.linetype == LineType.NODE_CLOSE: 838 if dl.closures > 1: 839 yield (dl.lineno, 840 'closing brace must be on its own line') 841 continue 842 if dl.linetype in (LineType.BLANK, LineType.COMMENT, 843 LineType.COMMENT_START, LineType.COMMENT_BODY, 844 LineType.COMMENT_END, LineType.PREPROCESSOR): 845 continue 846 text = _strip_strings_and_comments(dl.raw) 847 if '};' in text: 848 yield (dl.lineno, 849 'closing brace must be on its own line') 850 851 852def _display_col(text): 853 """Visual column width of text, with tabs expanded to the next 854 8-column stop, matching how printf and most editors render a 855 line and the kernel-wide line length convention.""" 856 col = 0 857 for ch in text: 858 if ch == '\t': 859 col = (col // 8 + 1) * 8 860 else: 861 col += 1 862 return col 863 864 865def check_line_length(ctx): 866 """Lines must not exceed 80 columns; tabs count as 8 (see 867 _display_col).""" 868 for dl in ctx.lines: 869 if dl.linetype == LineType.BLANK: 870 continue 871 cols = _display_col(dl.raw) 872 if cols > 80: 873 yield (dl.lineno, 874 'line exceeds 80 columns (%d)' % cols) 875 876 877def check_continuation_alignment(ctx): 878 """A multi-line property's continuation lines must align their 879 first non-whitespace character to the display column of the first 880 '<' or '"' after the '=' in the leading line. Display columns are 881 used so tab-indented .dts files (where a continuation aligns with 882 tabs plus spaces) are compared correctly.""" 883 for dl in ctx.lines: 884 if dl.linetype != LineType.PROPERTY: 885 continue 886 if not dl.continuations: 887 continue 888 eq = dl.raw.find('=') 889 if eq < 0: 890 continue 891 # First '<' or '"' after '=' 892 rest = dl.raw[eq + 1:] 893 m = re.search(r'[<"]', rest) 894 if not m: 895 continue 896 target_col = _display_col(dl.raw[:eq + 1 + m.start()]) 897 for cont in dl.continuations: 898 if _display_col(cont.indent_str) != target_col: 899 yield (cont.lineno, 900 'continuation should align to column %d ' 901 '(under "<" or \\")' % (target_col + 1)) 902 903 904def check_unclosed_block_comment(ctx): 905 """Every /* must have a matching */ in the same block. Catches both 906 a comment opened on its own line (COMMENT_START) and a tail comment 907 opened on a PROPERTY or other code line (where in_block_comment is 908 set by _split_code so the next line becomes COMMENT_BODY without a 909 preceding COMMENT_START).""" 910 open_lineno = None 911 for dl in ctx.lines: 912 if dl.linetype == LineType.COMMENT_START: 913 open_lineno = dl.lineno 914 elif dl.linetype == LineType.COMMENT_END: 915 open_lineno = None 916 elif dl.linetype == LineType.COMMENT_BODY and open_lineno is None: 917 # Block was opened by a /* tail on a code line; report at 918 # the first orphan body line since the originating line is 919 # already classified as something else. 920 open_lineno = dl.lineno 921 if open_lineno is not None: 922 yield (open_lineno, 'unclosed /* block comment') 923 924 925def check_unused_labels(ctx): 926 """Labels defined but never referenced are clutter.""" 927 defined, referenced = collect_labels_and_refs(ctx.text) 928 for label in sorted(defined - referenced): 929 # Find the line where this label is defined for line-number 930 # reporting. 931 m = re.search(r'(?m)^.*\b' + re.escape(label) + r'\s*:', ctx.text) 932 lineno = ctx.text[:m.start()].count('\n') + 1 if m else 1 933 yield (lineno, 'label %r defined but never &-referenced' % label) 934 935 936# --- registry -------------------------------------------------------------- 937 938RULES = [ 939 # 'relaxed' is the default; rules in this group must produce zero 940 # output on a clean kernel tree (post the small prep-cleanup 941 # commit at the head of this series). 942 Rule('trailing-whitespace', 'relaxed', 943 'no trailing whitespace on any line', 944 check_trailing_whitespace), 945 Rule('tab-in-yaml', 'relaxed', 946 'YAML (also DTS examples) may not contain tab characters', 947 check_tab_in_yaml_example, applies_to=('yaml',)), 948 Rule('mixed-indent-chars', 'relaxed', 949 'indent must not mix tabs and spaces', 950 check_mixed_indent_chars, applies_to=('dts', 'dtsi', 'dtso')), 951 Rule('unclosed-block-comment', 'relaxed', 952 'every /* block comment must close with */', 953 check_unclosed_block_comment), 954 955 # DTS files always use tabs; this is not negotiable per kernel 956 # coding style (.dts files are real source). Relaxed mode. 957 Rule('indent-unit-dts', 'relaxed', 958 'DTS files: 1 tab per nesting level', 959 check_indent_unit_dts, 960 applies_to=('dts', 'dtsi', 'dtso')), 961 962 # 'strict' rules are opt-in (e.g. for new submissions via 963 # checkpatch.pl in a follow-up series). They flag many existing 964 # files and can be promoted to relaxed once those are cleaned up. 965 Rule('indent-unit', 'strict', 966 'YAML: 2 or 4 spaces per level', 967 check_indent_unit_relaxed, applies_to=('yaml',)), 968 Rule('indent-unit-strict', 'strict', 969 'YAML: must be 4 spaces per level', 970 check_indent_unit_strict, applies_to=('yaml',)), 971 Rule('indent-consistent', 'strict', 972 'every line indented at depth * unit', 973 check_indent_consistent), 974 Rule('blank-lines', 'strict', 975 'no consecutive blanks; no blanks at node body edges', 976 check_blank_lines), 977 Rule('child-address-order', 'strict', 978 'addressed siblings must be in ascending address order', 979 check_child_address_order), 980 Rule('child-name-order', 'strict', 981 'unaddressed siblings must be in natural-sort name order', 982 check_child_name_order), 983 Rule('property-order', 'strict', 984 'canonical bucket + pairing + natural-sort order of properties', 985 check_property_order), 986 Rule('required-blank-lines', 'strict', 987 'blank line before child nodes and before "status"', 988 check_required_blank_lines), 989 Rule('hex-case', 'strict', 990 'hex literals must be lowercase', 991 check_hex_case), 992 Rule('unit-address-format', 'strict', 993 'unit addresses must be lowercase hex without leading zeros', 994 check_unit_address_format), 995 Rule('value-whitespace', 'strict', 996 'no whitespace directly inside <...> brackets', 997 check_value_whitespace), 998 Rule('node-close-alone', 'strict', 999 'closing brace must be on its own line', 1000 check_node_close_alone), 1001 Rule('line-length', 'strict', 1002 'lines must not exceed 80 columns', 1003 check_line_length), 1004 Rule('continuation-alignment', 'strict', 1005 'multi-line property continuations align under "<" or "\\""', 1006 check_continuation_alignment), 1007 Rule('unused-labels', 'strict', 1008 'every label must be &-referenced in the same example/file ' 1009 '(skipped for .dtsi/.dtso since labels there are exported)', 1010 check_unused_labels, applies_to=('yaml', 'dts')), 1011] 1012 1013 1014def select_rules(mode, input_kind): 1015 """Return rules that apply to the given mode and input type.""" 1016 rank = {'relaxed': 0, 'strict': 1} 1017 out = [] 1018 for r in RULES: 1019 if rank[r.mode] > rank[mode]: 1020 continue 1021 if input_kind not in r.applies_to: 1022 continue 1023 out.append(r) 1024 return out 1025 1026 1027# --------------------------------------------------------------------------- 1028# Block runner 1029# --------------------------------------------------------------------------- 1030 1031def check_block(text, mode, input_type): 1032 """Run all selected rules on a single block of DTS text. Returns a 1033 list of (lineno, rule_name, message) tuples.""" 1034 lines = classify_lines(text) 1035 ctx = Ctx(lines, text, mode, input_type) 1036 rules = select_rules(mode, input_type) 1037 findings = [] 1038 for r in rules: 1039 for lineno, msg in r.check(ctx): 1040 findings.append((lineno, r.name, msg)) 1041 findings.sort(key=lambda t: (t[0], t[1])) 1042 return findings 1043 1044 1045# --------------------------------------------------------------------------- 1046# Input drivers (YAML examples vs raw DTS) 1047# --------------------------------------------------------------------------- 1048 1049def _yaml_loader(): 1050 return ruamel.yaml.YAML() 1051 1052 1053def iter_yaml_examples(filepath): 1054 """Yield (example_text, base_lineno_in_file, example_index) tuples.""" 1055 yaml = _yaml_loader() 1056 try: 1057 with open(filepath, encoding='utf-8') as f: 1058 data = yaml.load(f) 1059 except Exception as e: 1060 print('%s: error loading YAML: %s' % (filepath, e), 1061 file=sys.stderr) 1062 return 1063 if not isinstance(data, dict) or 'examples' not in data: 1064 return 1065 examples = data['examples'] 1066 if not hasattr(examples, '__iter__'): 1067 return 1068 for i, ex in enumerate(examples): 1069 if not isinstance(ex, str): 1070 continue 1071 try: 1072 base = examples.lc.item(i)[0] + 2 1073 except Exception: 1074 base = 1 1075 yield (str(ex), base, i) 1076 1077 1078def iter_dts_file(filepath): 1079 """Treat the whole file as a single block.""" 1080 try: 1081 with open(filepath, encoding='utf-8') as f: 1082 text = f.read() 1083 except Exception as e: 1084 print('%s: error reading: %s' % (filepath, e), file=sys.stderr) 1085 return 1086 yield (text, 1, None) 1087 1088 1089# --------------------------------------------------------------------------- 1090# Top-level processing 1091# --------------------------------------------------------------------------- 1092 1093def input_kind(filepath): 1094 p = filepath.lower() 1095 if p.endswith('.yaml') or p.endswith('.yml'): 1096 return 'yaml' 1097 if p.endswith('.dts'): 1098 return 'dts' 1099 if p.endswith('.dtsi'): 1100 return 'dtsi' 1101 if p.endswith('.dtso'): 1102 return 'dtso' 1103 return None 1104 1105 1106# All input types that use tab indentation and follow DTS coding style. 1107DTS_FAMILY = ('dts', 'dtsi', 'dtso') 1108 1109 1110def collect_findings(filepath, mode): 1111 """Return a (lines, count) pair for filepath. lines is a list of 1112 formatted output strings; count is the number of findings.""" 1113 kind = input_kind(filepath) 1114 if kind == 'yaml': 1115 iterator = iter_yaml_examples(filepath) 1116 elif kind in DTS_FAMILY: 1117 iterator = iter_dts_file(filepath) 1118 else: 1119 return (['%s: unknown file type, skipping' % filepath], 0) 1120 1121 out = [] 1122 for text, base, idx in iterator: 1123 for lineno, rule, msg in check_block(text, mode, kind): 1124 abs_line = base + lineno - 1 1125 ex_tag = '' if idx is None else ' example %d' % idx 1126 out.append('%s:%d:%s [%s] %s' % 1127 (filepath, abs_line, ex_tag, rule, msg)) 1128 return (out, len(out)) 1129 1130 1131# Worker entry point for ProcessPoolExecutor.map(). Top-level so it is 1132# picklable on every platform. 1133def _worker(args): 1134 filepath, mode = args 1135 return collect_findings(filepath, mode) 1136 1137 1138def main(): 1139 import os 1140 ap = argparse.ArgumentParser( 1141 description='Check DTS coding style on YAML examples and ' 1142 '.dts/.dtsi/.dtso files.', 1143 fromfile_prefix_chars='@') 1144 ap.add_argument('--mode', choices=('relaxed', 'strict'), 1145 default='relaxed', 1146 help='which rule set to apply (default: relaxed)') 1147 ap.add_argument('-j', '--jobs', type=int, default=0, 1148 metavar='N', 1149 help='run N workers in parallel (default: respect ' 1150 'the make jobserver via $PARALLELISM, otherwise ' 1151 'os.cpu_count(); use 1 to disable multiprocessing)') 1152 ap.add_argument('--list-rules', action='store_true', 1153 help='print all rules with their mode and exit') 1154 ap.add_argument('files', nargs='*', metavar='file', 1155 help='YAML binding files or .dts/.dtsi/.dtso files; ' 1156 'use @argfile to read paths from a file') 1157 args = ap.parse_args() 1158 1159 if args.list_rules: 1160 for r in RULES: 1161 applies = ','.join(r.applies_to) 1162 print('%-22s %-7s [%s] %s' % 1163 (r.name, r.mode, applies, r.description)) 1164 return 0 1165 1166 if not args.files: 1167 ap.error('no input files') 1168 1169 if args.jobs > 0: 1170 jobs = args.jobs 1171 else: 1172 # When invoked under scripts/jobserver-exec, $PARALLELISM 1173 # holds the slot count make has reserved for us; this lets 1174 # `make -j N dt_binding_check` constrain our worker pool to N. 1175 try: 1176 jobs = int(os.environ['PARALLELISM']) 1177 except (KeyError, ValueError): 1178 jobs = os.cpu_count() or 1 1179 # Single-process path: keep import surface small for tests and 1180 # easy debugging. 1181 if jobs == 1 or len(args.files) == 1: 1182 total = 0 1183 for f in args.files: 1184 lines, n = collect_findings(f, args.mode) 1185 for line in lines: 1186 print(line, file=sys.stderr) 1187 total += n 1188 return 1 if total else 0 1189 1190 # Multi-process path. ex.map preserves input order so output is 1191 # deterministic across runs. 1192 from concurrent.futures import ProcessPoolExecutor 1193 total = 0 1194 work = [(f, args.mode) for f in args.files] 1195 chunk = max(1, len(work) // (jobs * 8)) if work else 1 1196 with ProcessPoolExecutor(max_workers=jobs) as ex: 1197 for lines, n in ex.map(_worker, work, chunksize=chunk): 1198 for line in lines: 1199 print(line, file=sys.stderr) 1200 total += n 1201 return 1 if total else 0 1202 1203 1204if __name__ == '__main__': 1205 sys.exit(main()) 1206