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