From nobody Mon Dec 1 23:03:13 2025 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CB9483019AA for ; Wed, 26 Nov 2025 10:17:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1764152228; cv=none; b=Pu+hhkhoeC1Ui+Y8e1Fu56rWEU5/pOZhLa59+z1gGt99/4ufhPzI3RoijxZpjOwSZpm8Z6oHb5+u1jUTdMR3a37W2S2GRJQk2YfPv0LQUFndncknF6RrKumFau8/8HQGotEtceqtm7yxRPxKcwSZLcfsri7utkLf47CRGJ7loRI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1764152228; c=relaxed/simple; bh=0LnwSaryQBMHk6XmHaGq2agr5IEiAwcbckke5AFC6tY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Q5FgfjGycsz6BlXh5P8w5eHHY9TASlpPyuJBqUT+Z1qIQGhxTkxwjuKjvOl8CGtNNfKeqJWxRPGHMFUeaMD7/R8uaKhFBb04wYDtKvWJ0yWP3kgz44+fXb4HYw6vgh4kjwg7XYmA5VNivZRPXGQAFkbVK+2K+bWMHByLnubOypE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1vOCZv-0000RJ-KC; Wed, 26 Nov 2025 11:16:51 +0100 Received: from moin.white.stw.pengutronix.de ([2a0a:edc0:0:b01:1d::7b] helo=bjornoya.blackshift.org) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vOCZv-002aSu-0t; Wed, 26 Nov 2025 11:16:51 +0100 Received: from hardanger.blackshift.org (p54b152ce.dip0.t-ipconnect.de [84.177.82.206]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) (Authenticated sender: mkl-all@blackshift.org) by smtp.blackshift.org (Postfix) with ESMTPSA id 0CC344A88DC; Wed, 26 Nov 2025 10:16:51 +0000 (UTC) From: Marc Kleine-Budde Date: Wed, 26 Nov 2025 11:16:12 +0100 Subject: [PATCH can-next v8 11/17] can: netlink: add PWM netlink interface Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20251126-canxl-v8-11-e7e3eb74f889@pengutronix.de> References: <20251126-canxl-v8-0-e7e3eb74f889@pengutronix.de> In-Reply-To: <20251126-canxl-v8-0-e7e3eb74f889@pengutronix.de> To: Marc Kleine-Budde , Vincent Mailhol , Oliver Hartkopp Cc: kernel@pengutronix.de, linux-can@vger.kernel.org, linux-kernel@vger.kernel.org, =?utf-8?q?St=C3=A9phane_Grosjean?= X-Mailer: b4 0.15-dev-a6db3 X-Developer-Signature: v=1; a=openpgp-sha256; l=12073; i=mkl@pengutronix.de; h=from:subject:message-id; bh=K/ytsfr3HPt0YSoH+C7734lsENMIj0Te9uS5x60/zjY=; b=owEBbQGS/pANAwAKAQx0Zd/5kJGcAcsmYgBpJtOFeslrHDjKSQdqdS5Zi68hF2yyh9kYYV2cW NHCehWVcBuJATMEAAEKAB0WIQSf+wzYr2eoX/wVbPMMdGXf+ZCRnAUCaSbThQAKCRAMdGXf+ZCR nPSGCACSGdszjGEEFd8BiLVct3eUQmWIepT2t7qYD/8wizDb6fu8lb0IkhIyIuqAKlQgPUd2dOj ljdtTDQJkHqHMGeD7XP6YAroiS7YSHj2ORDuLRZu2VpUS105CmCVPTzhmBrRSwu8ZIl2Qd5JUNL PtihwO0xteTSV/hgtyR6zCR737vD+Q57zssir3cIkuAaTbec7wGf+H+8wvRnHRKBHYkiTEmEKDu BIl5KdxaqYtx5HODUAlETIR7F60M3A6geWw4/MhPItBUyFEEQ80tmIKptc7+6lW2WpkoVDV70GW rysIdrFsmiq5w5s5fWpZO6rx8gwyW10Eh82tQMNyC/3HVnoV X-Developer-Key: i=mkl@pengutronix.de; a=openpgp; fpr=C1400BA0B3989E6FBC7D5B5C2B5EE211C58AEA54 X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: mkl@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-kernel@vger.kernel.org From: Vincent Mailhol 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, define 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. IFLA_CAN_PWM_PWM*_MIN and IFLA_CAN_PWM_PWM*_MAX define the range of allowed values and will match the value statically configured by the device in struct can_pwm_const. IFLA_CAN_PWM_PWM* match the runtime values stored in struct can_pwm. Those parameters may only be configured when the tms mode is on. If the PWMS, PWML and PWMO parameters are provided, check that all the needed parameters are present using can_validate_pwm(), then check their value using can_validate_pwm_bittiming(). PWMO defaults to zero if omitted. Otherwise, if CAN_CTRLMODE_XL_TMS is true but none of the PWM parameters are provided, calculate them using can_calc_pwm(). Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/netlink.c | 192 +++++++++++++++++++++++++++++++++++= +++- include/uapi/linux/can/netlink.h | 25 +++++ 2 files changed, 215 insertions(+), 2 deletions(-) diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c index b2c24439abba..d6b0e686fb11 100644 --- a/drivers/net/can/dev/netlink.c +++ b/drivers/net/can/dev/netlink.c @@ -25,6 +25,7 @@ static const struct nla_policy can_policy[IFLA_CAN_MAX + = 1] =3D { [IFLA_CAN_XL_DATA_BITTIMING] =3D { .len =3D sizeof(struct can_bittiming) = }, [IFLA_CAN_XL_DATA_BITTIMING_CONST] =3D { .len =3D sizeof(struct can_bitti= ming_const) }, [IFLA_CAN_XL_TDC] =3D { .type =3D NLA_NESTED }, + [IFLA_CAN_XL_PWM] =3D { .type =3D NLA_NESTED }, }; =20 static const struct nla_policy can_tdc_policy[IFLA_CAN_TDC_MAX + 1] =3D { @@ -39,6 +40,18 @@ static const struct nla_policy can_tdc_policy[IFLA_CAN_T= DC_MAX + 1] =3D { [IFLA_CAN_TDC_TDCF] =3D { .type =3D NLA_U32 }, }; =20 +static const struct nla_policy can_pwm_policy[IFLA_CAN_PWM_MAX + 1] =3D { + [IFLA_CAN_PWM_PWMS_MIN] =3D { .type =3D NLA_U32 }, + [IFLA_CAN_PWM_PWMS_MAX] =3D { .type =3D NLA_U32 }, + [IFLA_CAN_PWM_PWML_MIN] =3D { .type =3D NLA_U32 }, + [IFLA_CAN_PWM_PWML_MAX] =3D { .type =3D NLA_U32 }, + [IFLA_CAN_PWM_PWMO_MIN] =3D { .type =3D NLA_U32 }, + [IFLA_CAN_PWM_PWMO_MAX] =3D { .type =3D NLA_U32 }, + [IFLA_CAN_PWM_PWMS] =3D { .type =3D NLA_U32 }, + [IFLA_CAN_PWM_PWML] =3D { .type =3D NLA_U32 }, + [IFLA_CAN_PWM_PWMO] =3D { .type =3D NLA_U32 }, +}; + static int can_validate_bittiming(struct nlattr *data[], struct netlink_ext_ack *extack, int ifla_can_bittiming) @@ -119,6 +132,40 @@ static int can_validate_tdc(struct nlattr *data_tdc, return 0; } =20 +static int can_validate_pwm(struct nlattr *data[], + struct netlink_ext_ack *extack, u32 flags) +{ + struct nlattr *tb_pwm[IFLA_CAN_PWM_MAX + 1]; + int err; + + if (!data[IFLA_CAN_XL_PWM]) + return 0; + + if (!(flags & CAN_CTRLMODE_XL_TMS)) { + NL_SET_ERR_MSG(extack, "PWM requires TMS"); + return -EOPNOTSUPP; + } + + err =3D nla_parse_nested(tb_pwm, IFLA_CAN_PWM_MAX, data[IFLA_CAN_XL_PWM], + can_pwm_policy, extack); + if (err) + return err; + + if (!tb_pwm[IFLA_CAN_PWM_PWMS] !=3D !tb_pwm[IFLA_CAN_PWM_PWML]) { + NL_SET_ERR_MSG(extack, + "Provide either both PWMS and PWML, or none for automatic calcul= ation"); + return -EOPNOTSUPP; + } + + if (tb_pwm[IFLA_CAN_PWM_PWMO] && + (!tb_pwm[IFLA_CAN_PWM_PWMS] || !tb_pwm[IFLA_CAN_PWM_PWML])) { + NL_SET_ERR_MSG(extack, "PWMO requires both PWMS and PWML"); + return -EOPNOTSUPP; + } + + return 0; +} + static int can_validate_databittiming(struct nlattr *data[], struct netlink_ext_ack *extack, int ifla_can_data_bittiming, u32 flags) @@ -247,6 +294,10 @@ static int can_validate(struct nlattr *tb[], struct nl= attr *data[], if (err) return err; =20 + err =3D can_validate_pwm(data, extack, flags); + if (err) + return err; + return 0; } =20 @@ -322,6 +373,7 @@ static int can_ctrlmode_changelink(struct net_device *d= ev, sizeof(priv->fd.data_bittiming)); priv->ctrlmode &=3D ~CAN_CTRLMODE_XL_TDC_MASK; memset(&priv->xl.tdc, 0, sizeof(priv->xl.tdc)); + memset(&priv->xl.pwm, 0, sizeof(priv->xl.pwm)); } =20 can_set_default_mtu(dev); @@ -468,6 +520,76 @@ static int can_dbt_changelink(struct net_device *dev, = struct nlattr *data[], return 0; } =20 +static int can_pwm_changelink(struct net_device *dev, + const struct nlattr *pwm_nla, + struct netlink_ext_ack *extack) +{ + struct can_priv *priv =3D netdev_priv(dev); + const struct can_pwm_const *pwm_const =3D priv->xl.pwm_const; + struct nlattr *tb_pwm[IFLA_CAN_PWM_MAX + 1]; + struct can_pwm pwm =3D { 0 }; + int err; + + if (!(priv->ctrlmode & CAN_CTRLMODE_XL_TMS)) + return 0; + + if (!pwm_const) { + NL_SET_ERR_MSG(extack, "The device does not support PWM"); + return -EOPNOTSUPP; + } + + if (!pwm_nla) + return can_calc_pwm(dev, extack); + + err =3D nla_parse_nested(tb_pwm, IFLA_CAN_PWM_MAX, pwm_nla, + can_pwm_policy, extack); + if (err) + return err; + + if (tb_pwm[IFLA_CAN_PWM_PWMS]) { + pwm.pwms =3D nla_get_u32(tb_pwm[IFLA_CAN_PWM_PWMS]); + if (pwm.pwms < pwm_const->pwms_min || + pwm.pwms > pwm_const->pwms_max) { + NL_SET_ERR_MSG_FMT(extack, + "PWMS: %u tqmin is out of range: %u...%u", + pwm.pwms, pwm_const->pwms_min, + pwm_const->pwms_max); + return -EINVAL; + } + } + + if (tb_pwm[IFLA_CAN_PWM_PWML]) { + pwm.pwml =3D nla_get_u32(tb_pwm[IFLA_CAN_PWM_PWML]); + if (pwm.pwml < pwm_const->pwml_min || + pwm.pwml > pwm_const->pwml_max) { + NL_SET_ERR_MSG_FMT(extack, + "PWML: %u tqmin is out of range: %u...%u", + pwm.pwml, pwm_const->pwml_min, + pwm_const->pwml_max); + return -EINVAL; + } + } + + if (tb_pwm[IFLA_CAN_PWM_PWMO]) { + pwm.pwmo =3D nla_get_u32(tb_pwm[IFLA_CAN_PWM_PWMO]); + if (pwm.pwmo < pwm_const->pwmo_min || + pwm.pwmo > pwm_const->pwmo_max) { + NL_SET_ERR_MSG_FMT(extack, + "PWMO: %u tqmin is out of range: %u...%u", + pwm.pwmo, pwm_const->pwmo_min, + pwm_const->pwmo_max); + return -EINVAL; + } + } + + err =3D can_validate_pwm_bittiming(dev, &pwm, extack); + if (err) + return err; + + priv->xl.pwm =3D pwm; + return 0; +} + static int can_changelink(struct net_device *dev, struct nlattr *tb[], struct nlattr *data[], struct netlink_ext_ack *extack) @@ -559,6 +681,9 @@ static int can_changelink(struct net_device *dev, struc= t nlattr *tb[], =20 /* CAN XL */ err =3D can_dbt_changelink(dev, data, false, extack); + if (err) + return err; + err =3D can_pwm_changelink(dev, data[IFLA_CAN_XL_PWM], extack); if (err) return err; =20 @@ -647,6 +772,30 @@ static size_t can_ctrlmode_ext_get_size(void) nla_total_size(sizeof(u32)); /* IFLA_CAN_CTRLMODE_SUPPORTED */ } =20 +static size_t can_pwm_get_size(const struct can_pwm_const *pwm_const, + bool pwm_on) +{ + size_t size; + + if (!pwm_const || !pwm_on) + return 0; + + size =3D nla_total_size(0); /* nest IFLA_CAN_PWM */ + + size +=3D nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMS_MIN */ + size +=3D nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMS_MAX */ + size +=3D nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWML_MIN */ + size +=3D nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWML_MAX */ + size +=3D nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMO_MIN */ + size +=3D nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMO_MAX */ + + size +=3D nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMS */ + size +=3D nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWML */ + size +=3D nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMO */ + + return size; +} + static size_t can_get_size(const struct net_device *dev) { struct can_priv *priv =3D netdev_priv(dev); @@ -678,6 +827,8 @@ static size_t can_get_size(const struct net_device *dev) =20 size +=3D can_data_bittiming_get_size(&priv->xl, priv->ctrlmode & CAN_CTRLMODE_XL_TDC_MASK); + size +=3D can_pwm_get_size(priv->xl.pwm_const, /* IFLA_CAN_XL_PWM */ + priv->ctrlmode & CAN_CTRLMODE_XL_TMS); =20 return size; } @@ -776,6 +927,42 @@ static int can_tdc_fill_info(struct sk_buff *skb, cons= t struct net_device *dev, return -EMSGSIZE; } =20 +static int can_pwm_fill_info(struct sk_buff *skb, const struct can_priv *p= riv) +{ + const struct can_pwm_const *pwm_const =3D priv->xl.pwm_const; + const struct can_pwm *pwm =3D &priv->xl.pwm; + struct nlattr *nest; + + if (!pwm_const) + return 0; + + nest =3D nla_nest_start(skb, IFLA_CAN_XL_PWM); + if (!nest) + return -EMSGSIZE; + + if (nla_put_u32(skb, IFLA_CAN_PWM_PWMS_MIN, pwm_const->pwms_min) || + nla_put_u32(skb, IFLA_CAN_PWM_PWMS_MAX, pwm_const->pwms_max) || + nla_put_u32(skb, IFLA_CAN_PWM_PWML_MIN, pwm_const->pwml_min) || + nla_put_u32(skb, IFLA_CAN_PWM_PWML_MAX, pwm_const->pwml_max) || + nla_put_u32(skb, IFLA_CAN_PWM_PWMO_MIN, pwm_const->pwmo_min) || + nla_put_u32(skb, IFLA_CAN_PWM_PWMO_MAX, pwm_const->pwmo_max)) + goto err_cancel; + + if (priv->ctrlmode & CAN_CTRLMODE_XL_TMS) { + if (nla_put_u32(skb, IFLA_CAN_PWM_PWMS, pwm->pwms) || + nla_put_u32(skb, IFLA_CAN_PWM_PWML, pwm->pwml) || + nla_put_u32(skb, IFLA_CAN_PWM_PWMO, pwm->pwmo)) + goto err_cancel; + } + + nla_nest_end(skb, nest); + return 0; + +err_cancel: + nla_nest_cancel(skb, nest); + return -EMSGSIZE; +} + static int can_ctrlmode_ext_fill_info(struct sk_buff *skb, const struct can_priv *priv) { @@ -859,9 +1046,10 @@ static int can_fill_info(struct sk_buff *skb, const s= truct net_device *dev) priv->xl.data_bitrate_const, priv->xl.data_bitrate_const_cnt) || =20 - can_tdc_fill_info(skb, dev, IFLA_CAN_XL_TDC) - ) + can_tdc_fill_info(skb, dev, IFLA_CAN_XL_TDC) || =20 + can_pwm_fill_info(skb, priv) + ) return -EMSGSIZE; =20 return 0; diff --git a/include/uapi/linux/can/netlink.h b/include/uapi/linux/can/netl= ink.h index ebafb091d80f..c30d16746159 100644 --- a/include/uapi/linux/can/netlink.h +++ b/include/uapi/linux/can/netlink.h @@ -5,6 +5,7 @@ * Definitions for the CAN netlink interface * * Copyright (c) 2009 Wolfgang Grandegger + * Copyright (c) 2021-2025 Vincent Mailhol * * This program is free software; you can redistribute it and/or modify * it under the terms of the version 2 of the GNU General Public License @@ -147,6 +148,7 @@ enum { IFLA_CAN_XL_DATA_BITTIMING_CONST, IFLA_CAN_XL_DATA_BITRATE_CONST, IFLA_CAN_XL_TDC, + IFLA_CAN_XL_PWM, =20 /* add new constants above here */ __IFLA_CAN_MAX, @@ -188,6 +190,29 @@ enum { IFLA_CAN_CTRLMODE_MAX =3D __IFLA_CAN_CTRLMODE - 1 }; =20 +/* + * CAN FD/XL Pulse-Width Modulation (PWM) + * + * Please refer to struct can_pwm_const and can_pwm in + * include/linux/can/bittiming.h for further details. + */ +enum { + IFLA_CAN_PWM_UNSPEC, + IFLA_CAN_PWM_PWMS_MIN, /* u32 */ + IFLA_CAN_PWM_PWMS_MAX, /* u32 */ + IFLA_CAN_PWM_PWML_MIN, /* u32 */ + IFLA_CAN_PWM_PWML_MAX, /* u32 */ + IFLA_CAN_PWM_PWMO_MIN, /* u32 */ + IFLA_CAN_PWM_PWMO_MAX, /* u32 */ + IFLA_CAN_PWM_PWMS, /* u32 */ + IFLA_CAN_PWM_PWML, /* u32 */ + IFLA_CAN_PWM_PWMO, /* u32 */ + + /* add new constants above here */ + __IFLA_CAN_PWM, + IFLA_CAN_PWM_MAX =3D __IFLA_CAN_PWM - 1 +}; + /* u16 termination range: 1..65535 Ohms */ #define CAN_TERMINATION_DISABLED 0 =20 --=20 2.51.0