[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <5858d7d4-cee4-4838-94bd-b5bab8fb32b0@hartkopp.net>
Date: Sat, 29 Nov 2025 18:26:41 +0100
From: Oliver Hartkopp <socketcan@...tkopp.net>
To: Vincent Mailhol <mailhol@...nel.org>, netdev@...r.kernel.org,
Stephen Hemminger <stephen@...workplumber.org>,
Marc Kleine-Budde <mkl@...gutronix.de>, David Ahern <dsahern@...nel.org>
Cc: linux-kernel@...r.kernel.org, linux-can@...r.kernel.org
Subject: Re: [PATCH 7/7] iplink_can: add CAN XL's PWM interface
On 29.11.25 16:29, Vincent Mailhol wrote:
> This is the iproute2 counterpart of Linux kernel's commit 46552323fa67
> ("can: netlink: add PWM netlink interface").
>
> When the TMS is switched on, the node uses PWM (Pulse Width Modulation)
> during the data phase instead of the classic NRZ (Non Return to Zero)
> encoding.
>
> PWM is configured by three parameters:
>
> - PWMS: Pulse Width Modulation Short phase
> - PWML: Pulse Width Modulation Long phase
> - PWMO: Pulse Width Modulation Offset time
>
> For each of these parameters, the CAN netlink interface defines three IFLA
> symbols:
>
> - IFLA_CAN_PWM_PWM*_MIN: the minimum allowed value.
> - IFLA_CAN_PWM_PWM*_MAX: the maximum allowed value.
> - IFLA_CAN_PWM_PWM*: the runtime value.
>
> This results in a total of nine IFLA symbols which are all nested in a
> parent IFLA_CAN_XL_PWM symbol.
>
> Add the "pwms", "pwml" and "pwmo" options to iplink_can which controls the
> IFLA_CAN_PWM_PWM* runtime values.
>
> Add the logic to query and print all those IFLA values. Update
> print_usage() accordingly.
>
> Example using the dummy_can driver:
>
> # modprobe dummy_can
^^ two spaces
> # ip link set can0 type can bitrate 1000000 xl on xbitrate 20000000 tms on
> $ ip --details link show can0
> 5: can0: <NOARP> mtu 2060 qdisc noop state DOWN mode DEFAULT group default qlen 10
> link/can promiscuity 0 allmulti 0 minmtu 76 maxmtu 2060
> can <XL,XL-TMS> state STOPPED restart-ms 0
<XL,TMS>
Best regards,
Oliver
> bitrate 1000000 sample-point 0.750
> tq 6 prop-seg 59 phase-seg1 60 phase-seg2 40 sjw 20 brp 1
> dummy_can CC: tseg1 2..256 tseg2 2..128 sjw 1..128 brp 1..512 brp_inc 1
> dummy_can FD: dtseg1 2..256 dtseg2 2..128 dsjw 1..128 dbrp 1..512 dbrp_inc 1
> tdco 0..127 tdcf 0..127
> xbitrate 20000000 xsample-point 0.625
> xtq 6 xprop-seg 2 xphase-seg1 2 xphase-seg2 3 xsjw 1 xbrp 1
> pwms 2 pwml 6 pwmo 0
> dummy_can XL: xtseg1 2..256 xtseg2 2..128 xsjw 1..128 xbrp 1..512 xbrp_inc 1
> xtdco 0..127 xtdcf 0..127
> pwms 1..8 pwml 2..24 pwmo 0..16
> clock 160000000 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 gso_ipv4_max_size 65536 gro_ipv4_max_size 65536
>
> Signed-off-by: Vincent Mailhol <mailhol@...nel.org>
> ---
> ip/iplink_can.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 91 insertions(+), 1 deletion(-)
>
> diff --git a/ip/iplink_can.c b/ip/iplink_can.c
> index 3e7925e8..d43dc9bb 100644
> --- a/ip/iplink_can.c
> +++ b/ip/iplink_can.c
> @@ -34,7 +34,7 @@ static void print_usage(FILE *f)
> "\n"
> "\t[ xbitrate BITRATE [ xsample-point SAMPLE-POINT] ] |\n"
> "\t[ xtq TQ xprop-seg PROP_SEG xphase-seg1 PHASE-SEG1\n \t xphase-seg2 PHASE-SEG2 [ xsjw SJW ] ]\n"
> - "\t[ xtdcv TDCV xtdco TDCO xtdcf TDCF ]\n"
> + "\t[ xtdcv TDCV xtdco TDCO xtdcf TDCF pwms PWMS pwml PWML pwmo PWMO]\n"
> "\n"
> "\t[ loopback { on | off } ]\n"
> "\t[ listen-only { on | off } ]\n"
> @@ -67,6 +67,9 @@ static void print_usage(FILE *f)
> "\t TDCV := { NUMBER in mtq }\n"
> "\t TDCO := { NUMBER in mtq }\n"
> "\t TDCF := { NUMBER in mtq }\n"
> + "\t PWMS := { NUMBER in mtq }\n"
> + "\t PWML := { NUMBER in mtq }\n"
> + "\t PWMO := { NUMBER in mtq }\n"
> "\t RESTART-MS := { 0 | NUMBER in ms }\n"
> "\n"
> "\tUnits:\n"
> @@ -143,6 +146,7 @@ static int can_parse_opt(struct link_util *lu, int argc, char **argv,
> struct can_ctrlmode cm = { 0 };
> struct can_tdc fd = { .tdcv = -1, .tdco = -1, .tdcf = -1 };
> struct can_tdc xl = { .tdcv = -1, .tdco = -1, .tdcf = -1 };
> + __u32 pwms = -1, pwml = -1, pwmo = -1;
>
> while (argc > 0) {
> if (matches(*argv, "bitrate") == 0) {
> @@ -266,6 +270,18 @@ static int can_parse_opt(struct link_util *lu, int argc, char **argv,
> NEXT_ARG();
> if (get_u32(&xl.tdcf, *argv, 0))
> invarg("invalid \"xtdcf\" value", *argv);
> + } else if (matches(*argv, "pwms") == 0) {
> + NEXT_ARG();
> + if (get_u32(&pwms, *argv, 0))
> + invarg("invalid \"pwms\" value", *argv);
> + } else if (matches(*argv, "pwml") == 0) {
> + NEXT_ARG();
> + if (get_u32(&pwml, *argv, 0))
> + invarg("invalid \"pwml\" value", *argv);
> + } else if (matches(*argv, "pwmo") == 0) {
> + NEXT_ARG();
> + if (get_u32(&pwmo, *argv, 0))
> + invarg("invalid \"pwmo\" value", *argv);
> } else if (matches(*argv, "loopback") == 0) {
> NEXT_ARG();
> set_ctrlmode("loopback", *argv, &cm,
> @@ -401,6 +417,18 @@ static int can_parse_opt(struct link_util *lu, int argc, char **argv,
> addattr32(n, 1024, IFLA_CAN_TDC_TDCF, xl.tdcf);
> addattr_nest_end(n, tdc);
> }
> + if (pwms != -1 || pwml != -1 || pwmo != -1) {
> + struct rtattr *pwm = addattr_nest(n, 1024,
> + IFLA_CAN_XL_PWM | NLA_F_NESTED);
> +
> + if (pwms != -1)
> + addattr32(n, 1024, IFLA_CAN_PWM_PWMS, pwms);
> + if (pwml != -1)
> + addattr32(n, 1024, IFLA_CAN_PWM_PWML, pwml);
> + if (pwmo != -1)
> + addattr32(n, 1024, IFLA_CAN_PWM_PWMO, pwmo);
> + addattr_nest_end(n, pwm);
> + }
>
> return 0;
> }
> @@ -496,6 +524,62 @@ static void can_print_tdc_const_opt(struct rtattr *tdc_attr, bool is_xl)
> close_json_object();
> }
>
> +static void can_print_pwm_opt(struct rtattr *pwm_attr)
> +{
> + struct rtattr *tb[IFLA_CAN_PWM_MAX + 1];
> +
> + parse_rtattr_nested(tb, IFLA_CAN_PWM_MAX, pwm_attr);
> + if (tb[IFLA_CAN_PWM_PWMS] || tb[IFLA_CAN_PWM_PWML] ||
> + tb[IFLA_CAN_PWM_PWMO]) {
> + open_json_object("pwm");
> + can_print_nl_indent();
> + if (tb[IFLA_CAN_PWM_PWMS]) {
> + __u32 *pwms = RTA_DATA(tb[IFLA_CAN_PWM_PWMS]);
> +
> + print_uint(PRINT_ANY, " pwms", " pwms %u", *pwms);
> + }
> + if (tb[IFLA_CAN_PWM_PWML]) {
> + __u32 *pwml = RTA_DATA(tb[IFLA_CAN_PWM_PWML]);
> +
> + print_uint(PRINT_ANY, " pwml", " pwml %u", *pwml);
> + }
> + if (tb[IFLA_CAN_PWM_PWMO]) {
> + __u32 *pwmo = RTA_DATA(tb[IFLA_CAN_PWM_PWMO]);
> +
> + print_uint(PRINT_ANY, " pwmo", " pwmo %u", *pwmo);
> + }
> + close_json_object();
> + }
> +}
> +
> +static void can_print_pwm_const_opt(struct rtattr *pwm_attr)
> +{
> + struct rtattr *tb[IFLA_CAN_PWM_MAX + 1];
> +
> + parse_rtattr_nested(tb, IFLA_CAN_PWM_MAX, pwm_attr);
> + open_json_object("pwm");
> + can_print_nl_indent();
> + if (tb[IFLA_CAN_PWM_PWMS_MAX]) {
> + __u32 *pwms_min = RTA_DATA(tb[IFLA_CAN_PWM_PWMS_MIN]);
> + __u32 *pwms_max = RTA_DATA(tb[IFLA_CAN_PWM_PWMS_MAX]);
> +
> + can_print_timing_min_max("pwms", " pwms", *pwms_min, *pwms_max);
> + }
> + if (tb[IFLA_CAN_PWM_PWML_MAX]) {
> + __u32 *pwml_min = RTA_DATA(tb[IFLA_CAN_PWM_PWML_MIN]);
> + __u32 *pwml_max = RTA_DATA(tb[IFLA_CAN_PWM_PWML_MAX]);
> +
> + can_print_timing_min_max("pwml", " pwml", *pwml_min, *pwml_max);
> + }
> + if (tb[IFLA_CAN_PWM_PWMO_MAX]) {
> + __u32 *pwmo_min = RTA_DATA(tb[IFLA_CAN_PWM_PWMO_MIN]);
> + __u32 *pwmo_max = RTA_DATA(tb[IFLA_CAN_PWM_PWMO_MAX]);
> +
> + can_print_timing_min_max("pwmo", " pwmo", *pwmo_min, *pwmo_max);
> + }
> + close_json_object();
> +}
> +
> static void can_print_ctrlmode_ext(struct rtattr *ctrlmode_ext_attr,
> __u32 cm_flags)
> {
> @@ -735,6 +819,9 @@ static void can_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
> if (tb[IFLA_CAN_XL_TDC])
> can_print_tdc_opt(tb[IFLA_CAN_XL_TDC], true);
>
> + if (tb[IFLA_CAN_XL_PWM])
> + can_print_pwm_opt(tb[IFLA_CAN_XL_PWM]);
> +
> close_json_object();
> }
>
> @@ -759,6 +846,9 @@ static void can_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
> if (tb[IFLA_CAN_XL_TDC])
> can_print_tdc_const_opt(tb[IFLA_CAN_XL_TDC], true);
>
> + if (tb[IFLA_CAN_XL_PWM])
> + can_print_pwm_const_opt(tb[IFLA_CAN_XL_PWM]);
> +
> close_json_object();
> }
>
>
Powered by blists - more mailing lists