Documentation/driver-api/media/drivers/ccs/mk-ccs-regs GitHub 원문 ↗

Linux 6.18.37 · Driver API

mk-ccs-regs register definition generator

ccs-regs.asc를 register header, description array와 limit·capability C 파일로 변환하는 Perl generator 전체 해설입니다.

Source pathDocumentation/driver-api/media/drivers/ccs/mk-ccs-regs
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약과 해설

mk-ccs-regs:1-480

이 Perl script는 machine-readable CCS register definition을 C용 macro와 description table로 변환합니다. Kernel mode와 user-space mode의 type·bit 표현 차이를 흡수하고, 일반 register뿐 아니라 indexed·불연속 register와 limit capability를 같은 입력에서 생성합니다.

생성물 사이의 index, byte 크기, argument descriptor와 address formula는 모두 한 parser 상태에서 함께 만들어집니다. 따라서 생성된 C 파일을 직접 편집하지 않고 `ccs-regs.asc` 또는 이 generator를 수정한 뒤 전체 출력을 다시 만드는 것이 핵심입니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 #!/usr/bin/perl -w
2 # SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
3 # Copyright (C) 2019--2020 Intel Corporation
4
5 use Getopt::Long qw(:config no_ignore_case);
6 use File::Basename;
7
8 my $ccsregs = "ccs-regs.asc";
9 my $header;
10 my $regarray;
11 my $limitc;
12 my $limith;
13 my $kernel;
14 my $help;
15
16 GetOptions("ccsregs|c=s" => \$ccsregs,
17 "header|e=s" => \$header,
18 "regarray|r=s" => \$regarray,
19 "limitc|l=s" => \$limitc,
20 "limith|L=s" => \$limith,
21 "kernel|k" => \$kernel,
22 "help|h" => \$help) or die "can't parse options";
23
24 $help = 1 if ! defined $header || ! defined $limitc || ! defined $limith;
25
26 if (defined $help) {
27 print <<EOH
28 $0 - Create CCS register definitions for C
29
30 usage: $0 -c ccs-regs.asc -e header -r regarray -l limit-c -L limit-header [-k]
31
32 -c ccs register file
33 -e header file name
34 -r register description array file name
35 -l limit and capability array file name
36 -L limit and capability header file name
37 -k generate files for kernel space consumption
38 EOH
39 ;
40 exit 0;
41 }
42
43 my $lh_hdr = ! defined $kernel
44 ? '#include "ccs-os.h"' . "\n"
45 : "#include <linux/bits.h>\n#include <linux/types.h>\n";
46 my $uint32_t = ! defined $kernel ? 'uint32_t' : 'u32';
47 my $uint16_t = ! defined $kernel ? 'uint16_t' : 'u16';
48
49 open(my $R, "< $ccsregs") or die "can't open $ccsregs";
50
51 open(my $H, "> $header") or die "can't open $header";
52 my $A;
53 if (defined $regarray) {
54 open($A, "> $regarray") or die "can't open $regarray";
55 }
56 open(my $LC, "> $limitc") or die "can't open $limitc";
57 open(my $LH, "> $limith") or die "can't open $limith";
58
59 my %this;
60
61 sub is_limit_reg($) {
62 my $addr = hex $_[0];
63
64 return 0 if $addr < 0x40; # weed out status registers
65 return 0 if $addr >= 0x100 && $addr < 0xfff; # weed out configuration registers
66
67 return 1;
68 }
69
70 my $uc_header = basename uc $header;
71 $uc_header =~ s/[^A-Z0-9]/_/g;
72
73 my $copyright = "/* Copyright (C) 2019--2020 Intel Corporation */\n";
74 my $license = "SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause";
75 my $note = "/*\n * Generated by $0;\n * do not modify.\n */\n";
76
77 for my $fh ($A, $LC) {
78 print $fh "// $license\n$copyright$note\n" if defined $fh;
79 }
80
81 for my $fh ($H, $LH) {
82 print $fh "/* $license */\n$copyright$note\n";
83 }
84
85 print $H <<EOF
86 #ifndef __${uc_header}__
87 #define __${uc_header}__
88
89 EOF
90 ;
91
92 print $H <<EOF
93 #include <linux/bits.h>
94
95 #include <media/v4l2-cci.h>
96
97 EOF
98 if defined $kernel;
99
100 print $H "#define CCS_FL_BASE " .
101 (defined $kernel ? "CCI_REG_PRIVATE_SHIFT" : 16) . "\n";
102
103 my $flag = -1;
104 my $all_flags;
105
106 sub bit_def($) {
107 my $bit = shift @_;
108
109 if (defined $kernel) {
110 return "BIT$bit" if $bit =~ /^\(.*\)$/;
111 return "BIT($bit)";
112 }
113 return "(1U << $bit)";
114 }
115
116 sub flag_str($$) {
117 my ($flag, $check) = @_;
118
119 $$flag++;
120
121 my $flag_str = !$$flag ? "CCS_FL_BASE" : "(CCS_FL_BASE + $$flag)";
122
123 $flag_str = bit_def($flag_str);
124
125 $$check .= " | " if defined $$check;
126
127 $$check .= $flag_str;
128
129 return $flag_str;
130 }
131
132 if (! defined $kernel) {
133 print $H "#define CCS_FL_16BIT " . flag_str(\$flag, \$all_flags) . "\n";
134 print $H "#define CCS_FL_32BIT " . flag_str(\$flag, \$all_flags) . "\n";
135 }
136
137 print $H "#define CCS_FL_FLOAT_IREAL " . flag_str(\$flag, \$all_flags) . "\n";
138 print $H "#define CCS_FL_IREAL " . flag_str(\$flag, \$all_flags) . "\n";
139 print $H "#define CCS_BUILD_BUG \\
140 BUILD_BUG_ON(~CCI_REG_PRIVATE_MASK & ($all_flags))\n"
141 if defined $kernel;
142
143 print $H <<EOF
144
145 #define CCS_R_ADDR(r) ((r) & 0xffff)
146
147 EOF
148 if ! defined $kernel;
149
150 print $A <<EOF
151 #include <stdint.h>
152 #include <stdio.h>
153 #include "ccs-extra.h"
154 #include "ccs-regs.h"
155
156 EOF
157 if defined $A;
158
159 my $uc_limith = basename uc $limith;
160 $uc_limith =~ s/[^A-Z0-9]/_/g;
161
162 print $LH <<EOF
163 #ifndef __${uc_limith}__
164 #define __${uc_limith}__
165
166 $lh_hdr
167 struct ccs_limit {
168 $uint32_t reg;
169 $uint16_t size;
170 $uint16_t flags;
171 const char *name;
172 };
173
174 EOF
175 ;
176 print $LH "#define CCS_L_FL_SAME_REG " . bit_def(0) . "\n\n";
177
178 print $LH <<EOF
179 extern const struct ccs_limit ccs_limits[];
180
181 EOF
182 ;
183
184 print $LC <<EOF
185 #include "ccs-limits.h"
186 #include "ccs-regs.h"
187
188 const struct ccs_limit ccs_limits[] = {
189 EOF
190 ;
191
192 my $limitcount = 0;
193 my $argdescs;
194 my $reglist = "const struct ccs_reg_desc ccs_reg_desc[] = {\n";
195
196 sub name_split($$) {
197 my ($name, $addr) = @_;
198 my $args;
199
200 $name =~ /([^\(]+?)(\(.*)/;
201 ($name, $args) = ($1, $2);
202 $args = [split /,\s*/, $args];
203 foreach my $t (@$args) {
204 $t =~ s/[\(\)]//g;
205 $t =~ s/\//\\\//g;
206 }
207
208 return ($name, $addr, $args);
209 }
210
211 sub tabconv($) {
212 $_ = shift;
213
214 my @l = split "\n", $_;
215
216 map {
217 s/ {8,8}/\t/g;
218 s/\t\K +//;
219 } @l;
220
221 return (join "\n", @l) . "\n";
222 }
223
224 sub elem_bits(@) {
225 my @flags = @_;
226
227 return 16 if grep /^16$/, @flags;
228 return 32 if grep /^32$/, @flags;
229 return 8;
230 }
231
232 sub arr_size($) {
233 my $this = $_[0];
234 my $size = $this->{elsize};
235 my $h = $this->{argparams};
236
237 foreach my $arg (@{$this->{args}}) {
238 my $apref = $h->{$arg};
239
240 $size *= $apref->{max} - $apref->{min} + 1;
241 }
242
243 return $size;
244 }
245
246 sub print_args($$$) {
247 my ($this, $postfix, $is_same_reg) = @_;
248 my ($args, $argparams, $name) =
249 ($this->{args}, $this->{argparams}, $this->{name});
250 my $varname = "ccs_reg_arg_" . (lc $name) . $postfix;
251 my @mins;
252 my @sorted_args = @{$this->{sorted_args}};
253 my $lim_arg;
254 my $size = arr_size($this);
255
256 $argdescs .= "static const struct ccs_reg_arg " . $varname . "[] = {\n";
257
258 foreach my $sorted_arg (@sorted_args) {
259 push @mins, $argparams->{$sorted_arg}->{min};
260 }
261
262 foreach my $sorted_arg (@sorted_args) {
263 my $h = $argparams->{$sorted_arg};
264
265 $argdescs .= "\t{ \"$sorted_arg\", $h->{min}, $h->{max}, $h->{elsize} },\n";
266
267 $lim_arg .= defined $lim_arg ? ", $h->{min}" : "$h->{min}";
268 }
269
270 $argdescs .= "};\n\n";
271
272 $reglist .= "\t{ CCS_R_" . (uc $name) . "(" . (join ",", (@mins)) .
273 "), $size, sizeof($varname) / sizeof(*$varname)," .
274 " \"" . (lc $name) . "\", $varname },\n";
275
276 print $LC tabconv sprintf "\t{ CCS_R_" . (uc $name) . "($lim_arg), " .
277 $size . ", " . ($is_same_reg ? "CCS_L_FL_SAME_REG" : "0") .
278 ", \"$name" . (defined $this->{discontig} ? " $lim_arg" : "") . "\" },\n"
279 if is_limit_reg $this->{base_addr};
280 }
281
282 my $hdr_data;
283
284 while (<$R>) {
285 chop;
286 s/^\s*//;
287 next if /^[#;]/ || /^$/;
288 if (s/^-\s*//) {
289 if (s/^b\s*//) {
290 my ($bit, $addr) = split /\t+/;
291 $bit = uc $bit;
292 $hdr_data .= sprintf "#define %-62s %s", "CCS_" . (uc ${this{name}}) ."_$bit", bit_def($addr) . "\n";
293 } elsif (s/^f\s*//) {
294 s/[,\.-]/_/g;
295 my @a = split /\s+/;
296 my ($msb, $lsb, $this_field) = reverse @a;
297 @a = ( { "name" => "SHIFT", "addr" => $lsb, "fmt" => "%uU", },
298 { "name" => "MASK", "addr" => (1 << ($msb + 1)) - 1 - ((1 << $lsb) - 1), "fmt" => "0x%" . join(".", ($this{"elsize"} >> 2) x 2) . "x" } );
299 $this{"field"} = $this_field;
300 foreach my $ar (@a) {
301 #print $ar->{fmt}."\n";
302 $hdr_data .= sprintf "#define %-62s " . $ar->{"fmt"} . "\n", "CCS_" . (uc $this{"name"}) . (defined $this_field ? "_" . uc $this_field : "") . "_" . $ar->{"name"}, $ar->{"addr"} . "\n";
303 }
304 } elsif (s/^e\s*//) {
305 s/[,\.-]/_/g;
306 my ($enum, $addr) = split /\s+/;
307 $enum = uc $enum;
308 $hdr_data .= sprintf "#define %-62s %s", "CCS_" . (uc ${this{name}}) . (defined $this{"field"} ? "_" . uc $this{"field"} : "") ."_$enum", $addr . ($addr =~ /0x/i ? "" : "U") . "\n";
309 } elsif (s/^l\s*//) {
310 my ($arg, $min, $max, $elsize, @discontig) = split /\s+/;
311 my $size;
312
313 foreach my $num ($min, $max) {
314 $num = hex $num if $num =~ /0x/i;
315 }
316
317 $hdr_data .= sprintf "#define %-62s %s", "CCS_LIM_" . (uc ${this{name}} . "_MIN_$arg"), $min . ($min =~ /0x/i ? "" : "U") . "\n";
318 $hdr_data .= sprintf "#define %-62s %s", "CCS_LIM_" . (uc ${this{name}} . "_MAX_$arg"), $max . ($max =~ /0x/i ? "" : "U") . "\n";
319
320 my $h = $this{argparams};
321
322 $h->{$arg} = { "min" => $min,
323 "max" => $max,
324 "elsize" => $elsize =~ /^0x/ ? hex $elsize : $elsize,
325 "discontig" => \@discontig };
326
327 $this{discontig} = $arg if @discontig;
328
329 next if $#{$this{args}} + 1 != scalar keys %{$this{argparams}};
330
331 my $reg_formula = "$this{addr}";
332 my $lim_formula;
333
334 chop $reg_formula;
335
336 $reg_formula = "(" . $reg_formula if $this{flagstring} ne "";
337
338 foreach my $arg (@{$this{args}}) {
339 my $d = $h->{$arg}->{discontig};
340 my $times = $h->{$arg}->{elsize} != 1 ?
341 " * " . $h->{$arg}->{elsize} : "";
342
343 if (@$d) {
344 my ($lim, $offset) = split /,/, $d->[0];
345
346 $reg_formula .= " + (($arg) < $lim ? ($arg)$times : $offset + (($arg) - $lim)$times)";
347 } else {
348 $reg_formula .= " + ($arg)$times";
349 }
350
351 $lim_formula .= (defined $lim_formula ? " + " : "") . "($arg)$times";
352 }
353
354 $reg_formula .= ")";
355 $lim_formula =~ s/^\(([a-z0-9]+)\)$/$1/i;
356
357 print $H tabconv sprintf("#define %-62s %s", "CCS_R_" . (uc $this{name}) .
358 $this{arglist}, $reg_formula .
359 (($this{flagstring} eq "") ? "" :
360 " | " . $this{flagstring} . ")") . "\n");
361
362 print $H tabconv $hdr_data;
363 undef $hdr_data;
364
365 # Sort arguments in descending order by size
366 @{$this{sorted_args}} = sort {
367 $h->{$a}->{elsize} <= $h->{$b}->{elsize}
368 } @{$this{args}};
369
370 if (defined $this{discontig}) {
371 my $da = $this{argparams}->{$this{discontig}};
372 my ($first_discontig) = split /,/, $da->{discontig}->[0];
373 my $max = $da->{max};
374
375 $da->{max} = $first_discontig - 1;
376 print_args(\%this, "", 0);
377
378 $da->{min} = $da->{max} + 1;
379 $da->{max} = $max;
380 print_args(\%this, $first_discontig, 1);
381 } else {
382 print_args(\%this, "", 0);
383 }
384
385 next unless is_limit_reg $this{base_addr};
386
387 print $LH tabconv sprintf "#define %-63s%s\n",
388 "CCS_L_" . (uc $this{name}) . "_OFFSET(" .
389 (join ", ", @{$this{args}}) . ")", "($lim_formula)";
390 }
391
392 if (! @{$this{args}}) {
393 print $H tabconv($hdr_data);
394 undef $hdr_data;
395 }
396
397 next;
398 }
399
400 my ($name, $addr, @flags) = split /\t+/, $_;
401 my $args = [];
402
403 my $sp;
404
405 ($name, $addr, $args) = name_split($name, $addr) if /\(.*\)/;
406
407 $name =~ s/[,\.-]/_/g;
408
409 my $flagstring = "";
410 my $bits = elem_bits(@flags);
411 if (! defined $kernel) {
412 $flagstring .= "| CCS_FL_16BIT " if $bits == 16;
413 $flagstring .= "| CCS_FL_32BIT " if $bits == 32;
414 }
415 $flagstring .= "| CCS_FL_FLOAT_IREAL " if grep /^float_ireal$/, @flags;
416 $flagstring .= "| CCS_FL_IREAL " if grep /^ireal$/, @flags;
417 $flagstring =~ s/^\| //;
418 $flagstring =~ s/ $//;
419 $flagstring = "($flagstring)" if $flagstring =~ /\|/;
420 my $base_addr = $addr;
421 $addr = "CCI_REG$bits($addr)" if defined $kernel;
422
423 if ($flagstring ne "" && !@$args) {
424 $addr = "($addr | $flagstring)";
425 $flagstring = "";
426 }
427
428 my $arglist = @$args ? "(" . (join ", ", @$args) . ")" : "";
429 $hdr_data .= sprintf "#define %-62s %s\n", "CCS_R_" . (uc $name), $addr
430 if !@$args;
431
432 $name =~ s/\(.*//;
433
434 %this = ( name => $name,
435 addr => $addr,
436 flagstring => $flagstring,
437 base_addr => $base_addr,
438 argparams => {},
439 args => $args,
440 arglist => $arglist,
441 elsize => $bits / 8,
442 );
443
444 if (!@$args) {
445 $reglist .= "\t{ CCS_R_" . (uc $name) . ", 1, 0, \"" . (lc $name) . "\", NULL },\n";
446 print $H tabconv $hdr_data;
447 undef $hdr_data;
448
449 print $LC tabconv sprintf "\t{ CCS_R_" . (uc $name) . ", " .
450 $this{elsize} . ", 0, \"$name\" },\n"
451 if is_limit_reg $this{base_addr};
452 }
453
454 print $LH tabconv sprintf "#define %-63s%s\n",
455 "CCS_L_" . (uc $this{name}), $limitcount++
456 if is_limit_reg $this{base_addr};
457 }
458
459 if (defined $A) {
460 print $A $argdescs, $reglist;
461
462 print $A "\t{ 0 }\n";
463
464 print $A "};\n";
465 }
466
467 print $H "\n#endif /* __${uc_header}__ */\n";
468
469 print $LH tabconv sprintf "#define %-63s%s\n", "CCS_L_LAST", $limitcount;
470
471 print $LH "\n#endif /* __${uc_limith}__ */\n";
472
473 print $LC "\t{ 0 } /* Guardian */\n";
474 print $LC "};\n";
475
476 close($R);
477 close($H);
478 close($A) if defined $A;
479 close($LC);
480 close($LH);
481

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

실행 환경과 CLI option

1-39

이 파일은 warning을 켠 Perl script이며 `GPL-2.0-only OR BSD-3-Clause` 이중 라이선스로 배포됩니다. `Getopt::Long`은 option 대소문자를 구분하고, `File::Basename`은 출력 파일명으로 header guard를 만들 때 사용합니다.

입력 `ccs-regs.asc`는 기본값이 있지만 header, limit C source, limit header는 필수입니다. 필수값이 없거나 `-h`가 지정되면 help를 출력하고 종료합니다. `-r` register description array는 선택 사항이고 `-k`는 kernel space용 type·macro를 생성합니다. Option parsing 실패 시 `can't parse options` 오류로 중단합니다.

mk-ccs-regs CLI
Option필수 여부역할
-c / --ccsregs기본값 있음CCS register definition 입력
-e / --header필수Register header 출력
-r / --regarray선택Register description array 출력
-l / --limitc필수Limit·capability array C 출력
-L / --limith필수Limit·capability header 출력
-k / --kernel선택Kernel space용 출력
-h / --help선택Usage 출력 후 종료

짧은 option과 긴 option은 같은 변수로 연결됩니다.

#!/usr/bin/perl -w
# SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
# Copyright (C) 2019--2020 Intel Corporation

use Getopt::Long qw(:config no_ignore_case);
use File::Basename;

my $ccsregs = "ccs-regs.asc";
my $header;
my $regarray;
my $limitc;
my $limith;
my $kernel;
my $help;

GetOptions("ccsregs|c=s" => \$ccsregs,
           "header|e=s" => \$header,
           "regarray|r=s" => \$regarray,
           "limitc|l=s" => \$limitc,
           "limith|L=s" => \$limith,
           "kernel|k" => \$kernel,
           "help|h" => \$help) or die "can't parse options";

$help = 1 if ! defined $header || ! defined $limitc || ! defined $limith;

if (defined $help) {
        print <<EOH
$0 - Create CCS register definitions for C

usage: $0 -c ccs-regs.asc -e header -r regarray -l limit-c -L limit-header [-k]

        -c ccs register file
        -e header file name
        -r register description array file name
        -l limit and capability array file name
        -L limit and capability header file name
        -k generate files for kernel space consumption
EOH
          ;

출력 mode, 파일 준비와 limit 판별

40-80

Kernel mode가 아니면 limit header는 `ccs-os.h`, `uint32_t`, `uint16_t`를 사용합니다. Kernel mode에서는 `linux/bits.h`, `linux/types.h`, `u32`, `u16`으로 바뀝니다.

Script는 입력 register file을 읽기 mode로 열고 register header, 선택적 register array, limit C source, limit header를 쓰기 mode로 엽니다. 어느 파일이든 열 수 없으면 해당 `can't open` 오류로 즉시 중단합니다.

`is_limit_reg()`은 주소가 `0x40` 미만인 status register와 `0x100` 이상 `0xfff` 미만인 configuration register를 제외합니다. 나머지만 limit·capability register로 취급합니다. 생성물에는 동일한 SPDX license와 copyright, 생성 script 이름, 직접 수정하지 말라는 note를 기록합니다.

Generator 입출력 파일
HandleMode내용
RReadccs-regs.asc
HWriteRegister macro header
A선택적 Writeccs_reg_desc array
LCWriteccs_limits array C source
LHWriteccs_limit 선언과 index header

입력 하나에서 최대 네 종류의 C artifact를 만듭니다.

Limit register 주소 판별
Register base addressaddress < 0x40 ? status이므로 제외0x100 <= address < 0xfff ? configuration이므로 제외그 밖의 주소Limit·capability register로 포함

Status와 configuration 범위를 제거한 주소만 limit table에 넣습니다.

        exit 0;
}

my $lh_hdr = ! defined $kernel
        ? '#include "ccs-os.h"' . "\n"
        : "#include <linux/bits.h>\n#include <linux/types.h>\n";
my $uint32_t = ! defined $kernel ? 'uint32_t' : 'u32';
my $uint16_t = ! defined $kernel ? 'uint16_t' : 'u16';

open(my $R, "< $ccsregs") or die "can't open $ccsregs";

open(my $H, "> $header") or die "can't open $header";
my $A;
if (defined $regarray) {
        open($A, "> $regarray") or die "can't open $regarray";
}
open(my $LC, "> $limitc") or die "can't open $limitc";
open(my $LH, "> $limith") or die "can't open $limith";

my %this;

sub is_limit_reg($) {
        my $addr = hex $_[0];

        return 0 if $addr < 0x40; # weed out status registers
        return 0 if $addr >= 0x100 && $addr < 0xfff; # weed out configuration registers

        return 1;
}

my $uc_header = basename uc $header;
$uc_header =~ s/[^A-Z0-9]/_/g;

my $copyright = "/* Copyright (C) 2019--2020 Intel Corporation */\n";
my $license = "SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause";
my $note = "/*\n * Generated by $0;\n * do not modify.\n */\n";

for my $fh ($A, $LC) {
        print $fh "// $license\n$copyright$note\n" if defined $fh;
}

Header preamble과 register flag encoding

81-141

Register header와 limit header에는 license·copyright·generated note를 쓰고, 출력 파일명을 대문자화한 include guard를 만듭니다. Kernel mode의 register header는 `linux/bits.h`와 `media/v4l2-cci.h`를 include합니다.

`CCS_FL_BASE`는 user-space에서 bit 16, kernel에서 `CCI_REG_PRIVATE_SHIFT`를 기준으로 합니다. `bit_def()`은 kernel이면 `BIT()` 또는 이미 식인 경우 `BIT식`을, user-space이면 `(1U << bit)`를 생성합니다.

`flag_str()`은 다음 private flag bit를 할당하고 전체 check 식에 OR로 누적합니다. User-space는 16·32비트 폭 flag도 만들고, 양쪽 mode 모두 `FLOAT_IREAL`, `IREAL` flag를 만듭니다. Kernel mode는 `BUILD_BUG_ON`으로 누적 flag가 `CCI_REG_PRIVATE_MASK`를 넘지 않는지 검사합니다.

Private register flag 할당
CCS_FL_BASE 선택flag_str()가 index 증가bit_def()로 bit expression 생성all_flags에 OR 누적16BIT / 32BIT / FLOAT_IREAL / IREAL macroKernel: CCI_REG_PRIVATE_MASK BUILD_BUG_ON

CCS_FL_BASE 뒤에 순차 bit를 배정하고 kernel mask 범위를 검증합니다.

for my $fh ($H, $LH) {
        print $fh "/* $license */\n$copyright$note\n";
}

print $H <<EOF
#ifndef __${uc_header}__
#define __${uc_header}__

EOF
  ;

print $H <<EOF
#include <linux/bits.h>

#include <media/v4l2-cci.h>

EOF
        if defined $kernel;

print $H "#define CCS_FL_BASE                " .
    (defined $kernel ? "CCI_REG_PRIVATE_SHIFT" : 16) . "\n";

my $flag = -1;
my $all_flags;

sub bit_def($) {
        my $bit = shift @_;

        if (defined $kernel) {
                return "BIT$bit" if $bit =~ /^\(.*\)$/;
                return "BIT($bit)";
        }
        return "(1U << $bit)";
}

sub flag_str($$) {
        my ($flag, $check) = @_;

        $$flag++;

        my $flag_str = !$$flag ? "CCS_FL_BASE" : "(CCS_FL_BASE + $$flag)";

        $flag_str = bit_def($flag_str);

        $$check .= " | " if defined $$check;

        $$check .= $flag_str;

        return $flag_str;
}

if (! defined $kernel) {
        print $H "#define CCS_FL_16BIT                " . flag_str(\$flag, \$all_flags) . "\n";
        print $H "#define CCS_FL_32BIT                " . flag_str(\$flag, \$all_flags) . "\n";
}

print $H "#define CCS_FL_FLOAT_IREAL        " . flag_str(\$flag, \$all_flags) . "\n";
print $H "#define CCS_FL_IREAL                " . flag_str(\$flag, \$all_flags) . "\n";
print $H "#define CCS_BUILD_BUG \\
        BUILD_BUG_ON(~CCI_REG_PRIVATE_MASK & ($all_flags))\n"
    if defined $kernel;

생성되는 C structure와 배열 시작

142-195

User-space register header는 `CCS_R_ADDR(r)`로 하위 16비트 주소를 추출합니다. 선택적 register array source는 표준 정수 type, stdio, `ccs-extra.h`, `ccs-regs.h`를 include합니다.

Limit header는 kernel 또는 OS abstraction include 뒤에 `struct ccs_limit`을 정의합니다. 각 entry는 register 값, byte 크기, flag와 이름을 가지며, `CCS_L_FL_SAME_REG`는 불연속 index 구간이 같은 실제 register를 가리킴을 표시합니다.

Limit C source는 `ccs-limits.h`, `ccs-regs.h`를 include하고 `ccs_limits[]` 초기화를 시작합니다. 동시에 limit index counter, argument descriptor text, `ccs_reg_desc[]` 출력 buffer를 준비합니다.

생성 C data model
출력핵심 symbol내용
Register headerCCS_R_* / CCS_FL_*주소·field·enum·flag macro
Limit headerstruct ccs_limit / CCS_L_*Limit entry type와 index
Limit Cccs_limits[]Register·size·flag·name array
Register arrayccs_reg_desc[]일반 register와 argument descriptor

Header macro와 두 description array가 같은 register definition에서 파생됩니다.


print $H <<EOF

#define CCS_R_ADDR(r)                ((r) & 0xffff)

EOF
    if ! defined $kernel;

print $A <<EOF
#include <stdint.h>
#include <stdio.h>
#include "ccs-extra.h"
#include "ccs-regs.h"

EOF
        if defined $A;

my $uc_limith = basename uc $limith;
$uc_limith =~ s/[^A-Z0-9]/_/g;

print $LH <<EOF
#ifndef __${uc_limith}__
#define __${uc_limith}__

$lh_hdr
struct ccs_limit {
        $uint32_t reg;
        $uint16_t size;
        $uint16_t flags;
        const char *name;
};

EOF
  ;
print $LH "#define CCS_L_FL_SAME_REG        " . bit_def(0) . "\n\n";

print $LH <<EOF
extern const struct ccs_limit ccs_limits[];

EOF
  ;

print $LC <<EOF
#include "ccs-limits.h"
#include "ccs-regs.h"

const struct ccs_limit ccs_limits[] = {
EOF
  ;

my $limitcount = 0;
my $argdescs;
my $reglist = "const struct ccs_reg_desc ccs_reg_desc[] = {\n";

이름·공백·크기 helper

196-245

`name_split()`은 `name(arg, ...)` 형식에서 기본 이름과 argument 목록을 분리하고 괄호를 제거합니다. Argument 안의 slash는 생성 문자열에서 안전하도록 escape합니다.

`tabconv()`은 8개 공백 묶음을 tab으로 바꾸고 tab 뒤의 불필요한 공백을 제거해 생성 C code를 정렬합니다. `elem_bits()`은 flag에 16 또는 32가 있으면 해당 폭을 반환하고, 없으면 기본 8비트를 반환합니다.

`arr_size()`는 register element byte 크기에 각 argument의 `(max - min + 1)`을 모두 곱해 전체 배열 byte 크기를 계산합니다.

Parser helper 함수
함수입력결과
name_split이름과 괄호 argument기본 이름·주소·argument 배열
tabconv공백 정렬 문자열Tab 정규화 문자열
elem_bitsRegister flag 목록8 / 16 / 32 bit
arr_sizeElement 크기와 argument 범위전체 byte 크기

입력 이름과 폭을 C macro·array에 쓸 형태로 정규화합니다.

sub name_split($$) {
        my ($name, $addr) = @_;
        my $args;

        $name =~ /([^\(]+?)(\(.*)/;
        ($name, $args) = ($1, $2);
        $args = [split /,\s*/, $args];
        foreach my $t (@$args) {
                $t =~ s/[\(\)]//g;
                $t =~ s/\//\\\//g;
        }

        return ($name, $addr, $args);
}

sub tabconv($) {
        $_ = shift;

        my @l = split "\n", $_;

        map {
                s/ {8,8}/\t/g;
                s/\t\K +//;
        } @l;

        return (join "\n", @l) . "\n";
}

sub elem_bits(@) {
        my @flags = @_;

        return 16 if grep /^16$/, @flags;
        return 32 if grep /^32$/, @flags;
        return 8;
}

sub arr_size($) {
        my $this = $_[0];
        my $size = $this->{elsize};
        my $h = $this->{argparams};

        foreach my $arg (@{$this->{args}}) {
                my $apref = $h->{$arg};

                $size *= $apref->{max} - $apref->{min} + 1;
        }

        return $size;
}

Argument descriptor와 limit entry 생성

246-281

`print_args()`는 indexed register의 argument metadata를 C array로 출력합니다. 크기 순으로 정렬된 argument마다 이름, 최소값, 최대값, element stride를 `struct ccs_reg_arg` entry로 만들고 최소 argument 조합을 수집합니다.

이 최소 조합으로 `ccs_reg_desc` entry의 시작 register, 전체 배열 크기, argument 수와 이름을 만듭니다. Limit register이면 `ccs_limits[]` entry도 출력하며, 불연속 구간의 두 번째 조각에는 `CCS_L_FL_SAME_REG`를 설정하고 이름에 실제 limit argument를 덧붙입니다.

print_args() 출력 흐름
sorted_args와 argparams최소 argument 조합 수집ccs_reg_arg[] entry 생성arr_size()로 전체 크기 계산ccs_reg_desc[] entry 추가is_limit_reg() 확인ccs_limits[] entry와 SAME_REG flag 출력

한 indexed register에서 argument descriptor, register descriptor와 limit entry를 함께 만듭니다.

sub print_args($$$) {
        my ($this, $postfix, $is_same_reg) = @_;
        my ($args, $argparams, $name) =
          ($this->{args}, $this->{argparams}, $this->{name});
        my $varname = "ccs_reg_arg_" . (lc $name) . $postfix;
        my @mins;
        my @sorted_args = @{$this->{sorted_args}};
        my $lim_arg;
        my $size = arr_size($this);

        $argdescs .= "static const struct ccs_reg_arg " . $varname . "[] = {\n";

        foreach my $sorted_arg (@sorted_args) {
                push @mins, $argparams->{$sorted_arg}->{min};
        }

        foreach my $sorted_arg (@sorted_args) {
                my $h = $argparams->{$sorted_arg};

                $argdescs .= "\t{ \"$sorted_arg\", $h->{min}, $h->{max}, $h->{elsize} },\n";

                $lim_arg .= defined $lim_arg ? ", $h->{min}" : "$h->{min}";
        }

        $argdescs .= "};\n\n";

        $reglist .= "\t{ CCS_R_" . (uc $name) . "(" . (join ",", (@mins)) .
          "), $size, sizeof($varname) / sizeof(*$varname)," .
            " \"" . (lc $name) . "\", $varname },\n";

        print $LC tabconv sprintf "\t{ CCS_R_" . (uc $name) . "($lim_arg), " .
          $size . ", " . ($is_same_reg ? "CCS_L_FL_SAME_REG" : "0") .
            ", \"$name" . (defined $this->{discontig} ? " $lim_arg" : "") . "\" },\n"
              if is_limit_reg $this->{base_addr};
}

하위 record parser

282-330

Main loop는 입력을 한 줄씩 읽어 newline과 선행 공백을 제거하고 `#`, `;`, 빈 줄을 건너뜁니다. `-`로 시작하는 하위 record는 현재 register 문맥에서 type별로 처리합니다.

`- b`는 boolean bit를 `CCS_<REGISTER>_<BIT>` macro로, `- f`는 field의 SHIFT와 MASK macro로, `- e`는 현재 field 또는 register의 enum macro로 변환합니다. 이름의 comma, dot, hyphen은 underscore로 정규화합니다.

`- l`은 indexed argument의 min·max macro와 element stride·불연속 구간을 저장합니다. 16진 min/max는 숫자로 변환하고, 현재 register가 요구하는 모든 argument 정의가 모일 때까지 formula 생성을 미룹니다.

ccs-regs.asc 하위 record dispatch
Record입력 의미생성 결과
- bBoolean bitBIT 기반 CCS_* macro
- fLSB·MSB fieldSHIFT와 MASK macro
- eEnum name·valueField 또는 register enum macro
- lArgument min·max·strideCCS_LIM_*와 indexed address formula

각 접두사가 생성하는 C 정의와 parser 상태가 다릅니다.

my $hdr_data;

while (<$R>) {
        chop;
        s/^\s*//;
        next if /^[#;]/ || /^$/;
        if (s/^-\s*//) {
                if (s/^b\s*//) {
                        my ($bit, $addr) = split /\t+/;
                        $bit = uc $bit;
                        $hdr_data .= sprintf "#define %-62s %s", "CCS_" . (uc ${this{name}}) ."_$bit", bit_def($addr) . "\n";
                } elsif (s/^f\s*//) {
                        s/[,\.-]/_/g;
                        my @a = split /\s+/;
                        my ($msb, $lsb, $this_field) = reverse @a;
                        @a = ( { "name" => "SHIFT", "addr" => $lsb, "fmt" => "%uU", },
                               { "name" => "MASK", "addr" => (1 << ($msb + 1)) - 1 - ((1 << $lsb) - 1), "fmt" => "0x%" . join(".", ($this{"elsize"} >> 2) x 2) . "x" } );
                        $this{"field"} = $this_field;
                        foreach my $ar (@a) {
                                #print $ar->{fmt}."\n";
                                $hdr_data .= sprintf "#define %-62s " . $ar->{"fmt"} . "\n", "CCS_" . (uc $this{"name"}) . (defined $this_field ? "_" . uc $this_field : "") . "_" . $ar->{"name"}, $ar->{"addr"} . "\n";
                        }
                } elsif (s/^e\s*//) {
                        s/[,\.-]/_/g;
                        my ($enum, $addr) = split /\s+/;
                        $enum = uc $enum;
                        $hdr_data .= sprintf "#define %-62s %s", "CCS_" . (uc ${this{name}}) . (defined $this{"field"} ? "_" . uc $this{"field"} : "") ."_$enum", $addr . ($addr =~ /0x/i ? "" : "U") . "\n";
                } elsif (s/^l\s*//) {
                        my ($arg, $min, $max, $elsize, @discontig) = split /\s+/;
                        my $size;

                        foreach my $num ($min, $max) {
                                $num = hex $num if $num =~ /0x/i;
                        }

                        $hdr_data .= sprintf "#define %-62s %s", "CCS_LIM_" . (uc ${this{name}} . "_MIN_$arg"), $min . ($min =~ /0x/i ? "" : "U") . "\n";
                        $hdr_data .= sprintf "#define %-62s %s", "CCS_LIM_" . (uc ${this{name}} . "_MAX_$arg"), $max . ($max =~ /0x/i ? "" : "U") . "\n";

                        my $h = $this{argparams};

                        $h->{$arg} = { "min" => $min,
                                       "max" => $max,
                                       "elsize" => $elsize =~ /^0x/ ? hex $elsize : $elsize,
                                       "discontig" => \@discontig };

                        $this{discontig} = $arg if @discontig;

                        next if $#{$this{args}} + 1 != scalar keys %{$this{argparams}};

Indexed register와 불연속 주소 공식

331-390

모든 argument가 준비되면 parser는 base address에 각 `argument * element size`를 더해 register formula를 만듭니다. 불연속 정보가 있으면 기준보다 작은 index와 큰 index에 서로 다른 offset 공식을 사용합니다. Register flag가 있으면 전체 주소 식을 괄호로 묶고 마지막에 flag를 OR합니다.

생성한 `CCS_R_<NAME>(args)` macro 뒤에 누적된 field·enum macro를 출력합니다. Argument는 comment가 설명하듯 element size의 내림차순으로 정렬합니다.

불연속 argument는 첫 번째 연속 범위와 두 번째 범위로 min/max를 나눠 `print_args()`를 두 번 호출하며, 두 번째 entry는 같은 register를 공유한다고 표시합니다. Limit register에는 argument 조합에서 byte offset을 계산하는 `CCS_L_<NAME>_OFFSET(args)` macro도 생성합니다.

Indexed address formula 생성
Base address각 argument × element stride불연속 기준 존재?아니요: 선형 offset 누적예: 기준 전·후 offset 공식을 분기Register private flag ORCCS_R_NAME(args) macroLimit offset macro와 descriptor 생성

연속 배열과 불연속 배열을 같은 CCS_R_* API로 표현합니다.

        my $reg_formula = "$this{addr}";
        my $lim_formula;

        chop $reg_formula;

        $reg_formula = "(" . $reg_formula if $this{flagstring} ne "";

        foreach my $arg (@{$this{args}}) {
                my $d = $h->{$arg}->{discontig};
                my $times = $h->{$arg}->{elsize} != 1 ?
                  " * " . $h->{$arg}->{elsize} : "";

                if (@$d) {
                        my ($lim, $offset) = split /,/, $d->[0];

                        $reg_formula .= " + (($arg) < $lim ? ($arg)$times : $offset + (($arg) - $lim)$times)";
                } else {
                        $reg_formula .= " + ($arg)$times";
                }

                $lim_formula .= (defined $lim_formula ? " + " : "") . "($arg)$times";
        }

        $reg_formula .= ")";
        $lim_formula =~ s/^\(([a-z0-9]+)\)$/$1/i;

        print $H tabconv sprintf("#define %-62s %s", "CCS_R_" . (uc $this{name}) .
                                 $this{arglist}, $reg_formula .
                                 (($this{flagstring} eq "") ? "" :
                                  " | " . $this{flagstring} . ")") . "\n");

        print $H tabconv $hdr_data;
        undef $hdr_data;

        # Sort arguments in descending order by size
        @{$this{sorted_args}} = sort {
                $h->{$a}->{elsize} <= $h->{$b}->{elsize}
        } @{$this{args}};

        if (defined $this{discontig}) {
                my $da = $this{argparams}->{$this{discontig}};
                my ($first_discontig) = split /,/, $da->{discontig}->[0];
                my $max = $da->{max};

                $da->{max} = $first_discontig - 1;
                print_args(\%this, "", 0);

                $da->{min} = $da->{max} + 1;
                $da->{max} = $max;
                print_args(\%this, $first_discontig, 1);
        } else {
                print_args(\%this, "", 0);
        }

        next unless is_limit_reg $this{base_addr};

        print $LH tabconv sprintf "#define %-63s%s\n",
          "CCS_L_" . (uc $this{name}) . "_OFFSET(" .
            (join ", ", @{$this{args}}) . ")", "($lim_formula)";
}

최상위 register parser

391-442

Argument가 없는 하위 record 처리가 끝나면 누적 header data를 바로 출력합니다. 최상위 register 줄은 name, address와 flag 목록으로 분리하고, 괄호가 있으면 `name_split()`으로 indexed argument를 추출합니다.

Register 폭은 `elem_bits()`로 정하고 user-space에서는 16·32비트 flag를, 양쪽 mode에서는 `float_ireal`과 `ireal` flag를 조합합니다. Kernel mode 주소는 `CCI_REG<bits>(address)`로 감쌉니다.

Argument가 없는 register는 flag를 주소에 즉시 OR하고, indexed register는 formula 생성 단계까지 별도 `flagstring`으로 유지합니다. 정규화한 name, 주소, base address, argument metadata와 element byte 크기를 `%this` 현재-register 상태에 저장합니다.

최상위 register 상태 구성
name · address · flags 분리Indexed name이면 argument 추출문자 정규화8 / 16 / 32 bit 판정Kernel이면 CCI_REG<bits>() 적용Private flag 조합%this parser context 저장

한 register line이 이후 하위 record를 해석할 parser context가 됩니다.


        if (! @{$this{args}}) {
                print $H tabconv($hdr_data);
                undef $hdr_data;
        }

        next;
}

my ($name, $addr, @flags) = split /\t+/, $_;
my $args = [];

my $sp;

($name, $addr, $args) = name_split($name, $addr) if /\(.*\)/;

$name =~ s/[,\.-]/_/g;

my $flagstring = "";
my $bits = elem_bits(@flags);
if (! defined $kernel) {
        $flagstring .= "| CCS_FL_16BIT " if $bits == 16;
        $flagstring .= "| CCS_FL_32BIT " if $bits == 32;
}
$flagstring .= "| CCS_FL_FLOAT_IREAL " if grep /^float_ireal$/, @flags;
$flagstring .= "| CCS_FL_IREAL " if grep /^ireal$/, @flags;
$flagstring =~ s/^\| //;
$flagstring =~ s/ $//;
$flagstring = "($flagstring)" if $flagstring =~ /\|/;
my $base_addr = $addr;
$addr = "CCI_REG$bits($addr)" if defined $kernel;

if ($flagstring ne "" && !@$args) {
        $addr = "($addr | $flagstring)";
        $flagstring = "";
}

my $arglist = @$args ? "(" . (join ", ", @$args) . ")" : "";
$hdr_data .= sprintf "#define %-62s %s\n", "CCS_R_" . (uc $name), $addr
  if !@$args;

$name =~ s/\(.*//;

%this = ( name => $name,
          addr => $addr,
          flagstring => $flagstring,
          base_addr => $base_addr,
          argparams => {},
          args => $args,
          arglist => $arglist,
          elsize => $bits / 8,
        );

일반 register와 limit index

443-457

Argument가 없는 register는 `ccs_reg_desc[]`에 element 하나와 argument 없음으로 기록하고, header macro를 즉시 출력합니다. 주소가 limit 범위이면 `ccs_limits[]`에도 byte 크기와 이름을 추가합니다.

모든 limit register에는 현재 `limitcount`를 값으로 하는 `CCS_L_<NAME>` index macro를 만들고 counter를 증가시킵니다. 이 index는 생성된 `ccs_limits[]` 배열 위치와 일치합니다.


        if (!@$args) {
                $reglist .= "\t{ CCS_R_" . (uc $name) . ", 1,  0, \"" . (lc $name) . "\", NULL },\n";
                print $H tabconv $hdr_data;
                undef $hdr_data;

                print $LC tabconv sprintf "\t{ CCS_R_" . (uc $name) . ", " .
                  $this{elsize} . ", 0, \"$name\" },\n"
                    if is_limit_reg $this{base_addr};
        }

        print $LH tabconv sprintf "#define %-63s%s\n",
          "CCS_L_" . (uc $this{name}), $limitcount++
            if is_limit_reg $this{base_addr};
}

배열 종료, guard와 file close

458-480

Register array 출력이 요청되었다면 먼저 argument descriptor, 이어 register list를 쓰고 `{ 0 }` sentinel로 배열을 끝냅니다. Register header와 limit header는 각각 include guard를 닫습니다.

Limit header의 `CCS_L_LAST`에는 최종 limit count를 기록합니다. Limit C array는 `{ 0 } /* Guardian */` sentinel과 닫는 brace를 출력합니다. 마지막으로 입력, header, 선택적 register array, limit C와 limit header handle을 모두 닫습니다.

Generator 완료 순서
선택적 argdescs + ccs_reg_desc[] 출력Register array { 0 } sentinelRegister header include guard 종료CCS_L_LAST = limitcountLimit header include guard 종료ccs_limits[] Guardian sentinelR · H · A · LC · LH close

모든 buffer와 sentinel을 출력한 뒤 file handle을 닫습니다.


if (defined $A) {
        print $A $argdescs, $reglist;

        print $A "\t{ 0 }\n";

        print $A "};\n";
}

print $H "\n#endif /* __${uc_header}__ */\n";

print $LH tabconv sprintf "#define %-63s%s\n", "CCS_L_LAST", $limitcount;

print $LH "\n#endif /* __${uc_limith}__ */\n";

print $LC "\t{ 0 } /* Guardian */\n";
print $LC "};\n";

close($R);
close($H);
close($A) if defined $A;
close($LC);
close($LH);