#!/usr/bin/perl # SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) sub maxprefix { my $p = shift(@_); for (@_) { chop $p until /^\Q$p/; } $p =~ s/_[^_]*$/_/; $p = "CEC_OP_CEC_" if ($p =~ /CEC_OP_CEC_VERSION_/); return $p; } my $cur_msg; sub process_func { my $feature = shift; my $func = shift; my $func_args = $func; $func =~ s/\(.*//; my $msg = $func; $msg =~ s/([a-z])/\U\1/g; $func =~ s/cec_msg//; my $opt = $func; $opt =~ s/_([a-z])/\U\1/g; $func_args =~ s/.*\((.*)\).*/\1/; my $has_reply = $func_args =~ /^int reply/; $func_args =~ s/^int reply,? ?//; my $arg_names; my $arg_ptrs; my $name, $type, $size; my $msg_dash_name, $msg_lc_name; my @enum, $val; my $usage; my $has_digital = $func_args =~ /cec_op_digital_service_id/; my $has_ui_command = $func_args =~ /cec_op_ui_command/; my $has_short_aud_descr = $func_args =~ /num_descriptors/; my @ops_args = split(/, */, $func_args); if ($has_digital) { $func_args =~ s/const struct cec_op_digital_service_id \*digital/__u8 service_id_method, __u8 dig_bcast_system, __u16 transport_id, __u16 service_id, __u16 orig_network_id, __u16 program_number, __u8 channel_number_fmt, __u16 major, __u16 minor/; } if ($has_ui_command) { $func_args =~ s/const struct cec_op_ui_command \*ui_cmd/__u8 ui_cmd, __u8 has_opt_arg, __u8 play_mode, __u8 ui_function_media, __u8 ui_function_select_av_input, __u8 ui_function_select_audio_input, __u8 ui_bcast_type, __u8 ui_snd_pres_ctl, __u8 channel_number_fmt, __u16 major, __u16 minor/; } if ($has_short_aud_descr) { $func_args =~ s/const __u32 \*descriptors/__u8 descriptor1, __u8 descriptor2, __u8 descriptor3, __u8 descriptor4/; $func_args =~ s/const __u8 \*audio_format_id, const __u8 \*audio_format_code/__u8 audio_format_id1, __u8 audio_format_code1, __u8 audio_format_id2, __u8 audio_format_code2, __u8 audio_format_id3, __u8 audio_format_code3, __u8 audio_format_id4, __u8 audio_format_code4/; } my @args = split(/, */, $func_args); my $has_struct = $func_args =~ /struct/; return if ($func_args =~ /__u\d+\s*\*/); my $cec_msg = $msg; while ($cec_msg =~ /_/ && !exists($msgs{$cec_msg})) { $cec_msg =~ s/_[^_]*$//; } return unless ($cec_msg =~ /_/); my $msg_name = $cec_msg; $msg_name =~ s/CEC_MSG_//; $msg_dash_name = $msg; $msg_dash_name =~ s/CEC_MSG_//; $msg_dash_name =~ s/([A-Z])/\l\1/g; $msg_dash_name =~ s/_/-/g; $msg_lc_name = $msg; $msg_lc_name =~ s/([A-Z])/\l\1/g; $cur_msg = $msg; if ($cec_msg eq $msg) { if ($cec_msg =~ /_CDC_/ && !$cdc_case) { $cdc_case = 1; $logswitch .= "\tcase CEC_MSG_CDC_MESSAGE:\n"; $logswitch .= "\tswitch (msg->msg[4]) {\n"; } if ($cec_msg =~ /_HTNG_/ && !$htng_case) { $htng_case = 1; $cdc_case = 0; $std_logswitch = $logswitch; $logswitch = ""; } if ($cdc_case) { $cdcmsgtable .= "\t{ $cec_msg, \"$msg_name\" },\n"; } elsif ($htng_case) { $htngmsgtable .= "\t{ $cec_msg, \"$msg_name\" },\n"; } else { $msgtable .= "\t{ $cec_msg, \"$msg_name\" },\n"; } if (@args == 0) { $logswitch .= "\tcase $cec_msg:\n"; $logswitch .= "\t\tprintf(\"$msg_name (0x%02x)\\n\", $cec_msg);\n"; $logswitch .= "\t\tbreak;\n\n"; } else { $logswitch .= "\tcase $cec_msg: {\n"; foreach (@ops_args) { ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; if ($type =~ /struct .*\*/) { $type =~ s/ \*//; $type =~ s/const //; } if ($name eq "rc_profile" || $name eq "dev_features") { $logswitch .= "\t\tconst __u8 *$name = NULL;\n"; } elsif ($type eq "const char *") { $logswitch .= "\t\tchar $name\[16\];\n"; } elsif ($type eq "const __u32 *") { $logswitch .= "\t\t__u32 $name\[4\];\n"; } elsif ($type eq "const __u8 *") { $logswitch .= "\t\t__u8 $name\[4\];\n"; } elsif ($type =~ /struct/) { $logswitch .= "\t\t$type $name = {};\n"; } else { $logswitch .= "\t\t$type $name;\n"; } } if ($cdc_case) { $logswitch .= "\t\t__u16 phys_addr;\n"; } my $ops_lc_name = $msg_lc_name; $ops_lc_name =~ s/^cec_msg/cec_ops/; $logswitch .= "\n\t\t$ops_lc_name(msg"; if ($cdc_case) { $logswitch .= ", &phys_addr"; } foreach (@ops_args) { ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; if ($type eq "const char *" || $type eq "const __u8 *" || $type eq "const __u32 *") { $logswitch .= ", $name"; } else { $logswitch .= ", &$name"; } } $logswitch .= ");\n"; $logswitch .= "\t\tprintf(\"$msg_name (0x%02x):\\n\", $cec_msg);\n"; if ($cdc_case) { $logswitch .= "\t\tlog_arg(&arg_phys_addr, \"phys-addr\", phys_addr);\n"; } foreach (@ops_args) { ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; my $dash_name = $name; $dash_name =~ s/_/-/g; if ($name eq "rc_profile" || $name eq "dev_features") { $logswitch .= "\t\tlog_features(&arg_$name, \"$dash_name\", $name);\n"; } elsif ($name eq "digital") { $logswitch .= "\t\tlog_digital(\"$dash_name\", &$name);\n"; } elsif ($name eq "ui_cmd") { $logswitch .= "\t\tlog_ui_command(\"$dash_name\", &$name);\n"; } elsif ($name eq "vendor_id") { $logswitch .= "\t\tlog_vendor_id(\"$dash_name\", $name);\n"; } elsif ($name eq "rec_src") { $logswitch .= "\t\tlog_rec_src(\"$dash_name\", &$name);\n"; } elsif ($name eq "tuner_dev_info") { $logswitch .= "\t\tlog_tuner_dev_info(\"$dash_name\", &$name);\n"; } elsif ($name eq "descriptors") { $logswitch .= "\t\tlog_descriptors(\"$dash_name\", num_descriptors, $name);\n"; } elsif ($name eq "audio_format_id" || $name eq "audio_format_code") { $logswitch .= "\t\tlog_u8_array(\"$dash_name\", num_descriptors, $name);\n"; } else { $logswitch .= "\t\tlog_arg(&arg_$name, \"$dash_name\", $name);\n"; } } $logswitch .= "\t\tbreak;\n\t}\n"; } } return if $has_struct; $options .= "\tOpt$opt,\n"; $messages .= "\t\t$cec_msg,\n"; if (@args == 0) { $messages .= "\t\t0, { }, { },\n"; $long_opts .= "\t{ \"$msg_dash_name\", no_argument, 0, Opt$opt }, \\\n"; $usage .= "\t\" --" . sprintf("%-30s", $msg_dash_name) . "Send $msg_name message (\" xstr($cec_msg) \")\\n\"\n"; $usage_msg{$msg} = $usage; $switch .= "\tcase Opt$opt: {\n"; $switch .= "\t\t$msg_lc_name(&msg"; $switch .= ", reply" if $has_reply; $switch .= ");\n\t\tbreak;\n\t}\n\n"; } else { $long_opts .= "\t{ \"$msg_dash_name\", required_argument, 0, Opt$opt }, \\\n"; $usage .= "\t\" --$msg_dash_name"; my $prefix = "\t\" " . sprintf("%-30s", " "); my $sep = " "; foreach (@args) { ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; $name =~ s/_/-/g; $usage .= "$sep$name="; $sep = ","; } $usage .= "\\n\"\n"; foreach (@args) { ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; @enum = @{$types{$name}}; next if !scalar(@enum); $name =~ s/_/-/g; $usage .= $prefix . "'$name' can have these values:\\n\"\n"; my $common_prefix = maxprefix(@enum); foreach (@enum) { my $e = $_; s/^$common_prefix//; s/([A-Z])/\l\1/g; s/_/-/g; $usage .= $prefix . " $_ (\" xstr($e) \")\\n\"\n"; } } $usage .= $prefix . "Send $msg_name message (\" xstr($cec_msg) \")\\n\"\n"; $usage_msg{$msg} = $usage; $switch .= "\tcase Opt$opt: {\n"; foreach (@args) { ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; if ($type =~ /char/) { $switch .= "\t\tconst char *$name = \"\";\n"; } else { $switch .= "\t\t$type $name = 0;\n"; } } $switch .= "\n\t\twhile (*subs != '\\0') {\n"; $switch .= "\t\t\tswitch (cec_parse_subopt(&subs, opt->arg_names, &value)) {\n"; my $cnt = 0; foreach (@args) { ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; @enum = @{$types{$name}}; $switch .= "\t\t\tcase $cnt:\n"; if ($type =~ /char/) { $switch .= "\t\t\t\t$name = value;\n"; } elsif (scalar(@enum) || $name eq "ui_cmd") { $switch .= "\t\t\t\t$name = parse_enum(value, opt->args\[$cnt\]);\n"; } elsif ($name =~ /audio_out_delay/ || $name =~ /video_latency/) { $switch .= "\t\t\t\t$name = parse_latency(value);\n"; } elsif ($name =~ /phys_addr/) { $switch .= "\t\t\t\t$name = cec_parse_phys_addr(value);\n"; } else { $switch .= "\t\t\t\t$name = strtol(value, 0L, 0);\n"; } $switch .= "\t\t\t\tbreak;\n"; $cnt++; } $switch .= "\t\t\tdefault:\n"; $switch .= "\t\t\t\texit(1);\n"; $switch .= "\t\t\t}\n\t\t}\n"; $switch .= "\t\t$msg_lc_name(&msg"; $switch .= ", reply" if $has_reply; foreach (@args) { ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; $switch .= ", $name"; } $switch .= ");\n\t\tbreak;\n\t}\n\n"; foreach (@args) { ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; if ($arg_names ne "") { $arg_names .= ", "; $arg_ptrs .= ", "; } $arg_ptrs .= "&arg_$name"; $name =~ s/_/-/g; $arg_names .= '"' . $name . '"'; } $size = $#args + 1; $messages .= "\t\t$size, { $arg_names },\n"; $messages .= "\t\t{ $arg_ptrs },\n"; foreach (@args) { ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; @enum = @{$types{$name}}; $size = scalar(@enum); if ($size && !defined($created_enum{$name})) { $created_enum{$name} = 1; $enums .= "static const struct cec_arg_enum_values type_$name\[\] = {\n"; my $common_prefix = maxprefix(@enum); foreach (@enum) { $val = $_; s/^$common_prefix//; s/([A-Z])/\l\1/g; s/_/-/g; $enums .= "\t{ \"$_\", $val },\n"; } $enums .= "};\n\n"; } if (!defined($created_arg{$name})) { $created_arg{$name} = 1; if ($type eq "__u8" && $size) { $arg_structs .= "static const struct cec_arg arg_$name = {\n"; $arg_structs .= "\tCEC_ARG_TYPE_ENUM, $size, type_$name\n};\n\n"; } elsif ($type eq "__u8") { $arg_structs .= "#define arg_$name arg_u8\n"; } elsif ($type eq "__u16") { $arg_structs .= "#define arg_$name arg_u16\n"; } elsif ($type eq "__u32") { $arg_structs .= "#define arg_$name arg_u32\n"; } elsif ($type eq "const char *") { $arg_structs .= "#define arg_$name arg_string\n"; } } } } $messages .= "\t\t\"$msg_name\"\n"; $messages .= "\t}, {\n"; push @{$feature_usage{$feature}}, $msg; } while (<>) { last if /\/\* Messages \*\//; } $comment = 0; $has_also = 0; $operand_name = ""; $feature = ""; while (<>) { chomp; last if /_CEC_UAPI_FUNCS_H/; if (/^\/\*.*Feature \*\/$/) { ($feature) = /^\/\* (.*) Feature/; } elsif (/^\/\*.*General Protocol Messages \*\/$/) { $feature = "Abort"; } if ($operand_name ne "" && !/^#define/) { @{$types{$operand_name}} = @ops; undef @ops; $operand_name = ""; } if (/\/\*.*Operand \((.*)\)/) { $operand_name = $1; next; } s/\/\*.*\*\///; if ($comment) { if ($has_also) { if (/CEC_MSG/) { ($also_msg) = /(CEC_MSG\S+)/; push @{$feature_also{$feature}}, $also_msg; if (!exists($feature_usage{$feature})) { push @{$feature_usage{$feature}}, ""; } } } elsif (/^ \* Has also:$/) { $has_also = 1; } $has_also = 0 if (/\*\//); next unless /\*\//; $comment = 0; s/^.*\*\///; } if (/\/\*/) { $comment = 1; $has_also = 0; next; } next if /^\s*$/; if (/^\#define/) { ($name, $val) = /define (\S+)\s+(\S+)/; if ($name =~ /^CEC_MSG/) { $msgs{$name} = 1; } elsif ($operand_name ne "" && $name =~ /^CEC_OP/) { push @ops, $name; } next; } } while (<>) { chomp; if (/^\/\*.*Feature \*\/$/) { ($feature) = /^\/\* (.*) Feature/; } elsif (/^\/\*.*General Protocol Messages \*\/$/) { $feature = "Abort"; } if (/\/\* broadcast \*\//) { $usage_msg{$cur_msg} =~ s/"\)\\n"$/", bcast)\\n"/; } s/\/\*.*\*\///; if ($comment) { next unless /\*\//; $comment = 0; s/^.*\*\///; } if (/\/\*/) { $comment = 1; next; } next if /^\s*$/; next if /cec_msg_reply_feature_abort/; next if /cec_msg_htng_init/; if (/^static (__)?inline(__)? void cec_msg.*\(.*\)/) { s/static\s(__)?inline(__)?\svoid\s//; s/struct cec_msg \*msg, //; s/struct cec_msg \*msg//; process_func($feature, $_); next; } if (/^static (__)?inline(__)? void cec_msg/) { $func = $_; next; } if ($func ne "") { $func .= $_; next unless /\)$/; $func =~ s/\s+/ /g; $func =~ s/static\s(__)?inline(__)?\svoid\s//; $func =~ s/struct cec_msg \*msg, //; $func =~ s/struct cec_msg \*msg//; process_func($feature, $func); $func = ""; } } $options .= "\tOptHelpAll,\n"; open(my $fh, '>', 'cec-parse-src-gen.h') or die "Could not open cec-parse-src-gen.h for writing"; print $fh "\n\n"; foreach (sort keys %feature_usage) { $name = $_; s/ /_/g; s/([A-Z])/\l\1/g; $usage_var = $_ . "_usage"; printf $fh "static const char *$usage_var =\n"; $usage = ""; foreach (@{$feature_usage{$name}}) { $usage .= $usage_msg{$_}; } foreach (@{$feature_also{$name}}) { $usage .= $usage_msg{$_}; } chop $usage; $usage =~ s/" --vendor-remote-button-up/VENDOR_EXTRA\n\t" --vendor-remote-button-up/; printf $fh "%s;\n\n", $usage; s/_/-/g; $opt = "OptHelp" . $name; $opt =~ s/ //g; $help .= "\tif (options[OptHelpAll] || options\[$opt\]) {\n"; $help .= "\t\tprintf(\"$name Feature:\\n\\n\");\n"; $help .= "\t\tprintf(\"\%s\\n\", $usage_var);\n\t}\n"; } printf $fh "void cec_parse_usage_options(const char *options)\n{\n"; printf $fh "%s}\n\n", $help; printf $fh "void cec_parse_msg_args(struct cec_msg &msg, int reply, const cec_msg_args *opt, int ch)\n{\n"; printf $fh "\tchar *value, *subs = optarg;\n\n"; printf $fh "\tswitch (ch) {\n"; $switch =~ s/(service_id_method, dig_bcast_system, transport_id, service_id, orig_network_id, program_number, channel_number_fmt, major, minor)/args2digital_service_id(\1)/g; $switch =~ s/(ui_cmd, has_opt_arg, play_mode, ui_function_media, ui_function_select_av_input, ui_function_select_audio_input, ui_bcast_type, ui_snd_pres_ctl, channel_number_fmt, major, minor)/args2ui_command(\1)/g; $switch =~ s/(descriptor1, descriptor2, descriptor3, descriptor4)/args2short_descrs(\1)/g; $switch =~ s/(audio_format_id1, audio_format_code1, audio_format_id2, audio_format_code2, audio_format_id3, audio_format_code3, audio_format_id4, audio_format_code4)/args2short_aud_fmt_ids(audio_format_id1, audio_format_id2, audio_format_id3, audio_format_id4), args2short_aud_fmt_codes(audio_format_code1, audio_format_code2, audio_format_code3, audio_format_code4)/g; printf $fh "%s", $switch; printf $fh "\t}\n};\n\n"; close $fh; open(my $fh, '>', 'cec-parse-gen.h') or die "Could not open cec-parse-gen.h for writing"; foreach (sort keys %feature_usage) { $name = $_; s/ /-/g; s/([A-Z])/\l\1/g; $help_features .= sprintf("\t\" --help-%-28s Show help for the $name feature\\n\" \\\n", $_); $opt = "OptHelp" . $name; $opt =~ s/ //g; $options .= "\t$opt,\n"; $long_opts .= "\t{ \"help-$_\", no_argument, 0, $opt }, \\\n"; } print $fh "enum cec_parse_options {\n\tOptMessages = 255,\n"; printf $fh "%s\n\tOptLast = 512\n};\n\n", $options; printf $fh "#define CEC_PARSE_LONG_OPTS \\\n%s\n\n", $long_opts; printf $fh "#define CEC_PARSE_USAGE \\\n%s\n\n", $help_features; close $fh; open(my $fh, '>', 'cec-log-gen.h') or die "Could not open cec-log-gen.h for writing"; printf $fh "%s%s\n", $enums, $arg_structs; printf $fh "static const struct cec_msg_args messages[] = {\n\t{\n"; printf $fh "%s\t}\n};\n\n", $messages; print $fh <<'EOF'; void cec_log_msg(const struct cec_msg *msg) { if (msg->len == 1) { printf("POLL\n"); goto status; } switch (msg->msg[1]) { EOF printf $fh "%s", $std_logswitch; print $fh <<'EOF'; default: log_unknown_msg(msg); break; } break; default: log_unknown_msg(msg); break; } status: if ((msg->tx_status && !(msg->tx_status & CEC_TX_STATUS_OK)) || (msg->rx_status && !(msg->rx_status & (CEC_RX_STATUS_OK | CEC_RX_STATUS_FEATURE_ABORT)))) printf("\t%s\n", cec_status2s(*msg).c_str()); } static void log_htng_msg(const struct cec_msg *msg) { if ((msg->tx_status && !(msg->tx_status & CEC_TX_STATUS_OK)) || (msg->rx_status && !(msg->rx_status & (CEC_RX_STATUS_OK | CEC_RX_STATUS_FEATURE_ABORT)))) printf("\t%s\n", cec_status2s(*msg).c_str()); if (msg->len < 6) return; switch (msg->msg[5]) { EOF printf $fh "%s", $logswitch; print $fh <<'EOF'; default: log_htng_unknown_msg(msg); break; } } EOF close $fh; open(my $fh, '>', 'cec-msgs-gen.h') or die "Could not open cec-msgs-gen.h for writing"; printf $fh "struct msgtable {\n"; printf $fh "\t__u8 opcode;\n"; printf $fh "\tconst char *name;\n"; printf $fh "};\n\n"; printf $fh "static const struct msgtable msgtable[] = {\n"; printf $fh "%s", $msgtable; printf $fh "\t{ CEC_MSG_VENDOR_COMMAND, \"VENDOR_COMMAND\" },\n"; printf $fh "\t{ CEC_MSG_VENDOR_COMMAND_WITH_ID, \"VENDOR_COMMAND_WITH_ID\" },\n"; printf $fh "\t{ CEC_MSG_VENDOR_REMOTE_BUTTON_DOWN, \"VENDOR_REMOTE_BUTTON_DOWN\" },\n"; printf $fh "\t{ CEC_MSG_CDC_MESSAGE, \"CDC_MESSAGE\" },\n"; printf $fh "};\n\n"; printf $fh "static const struct msgtable cdcmsgtable[] = {\n"; printf $fh "%s", $cdcmsgtable; printf $fh "};\n\n"; printf $fh "static const struct msgtable htngmsgtable[] = {\n"; printf $fh "%s", $htngmsgtable; printf $fh "};\n"; close $fh;