diff options
Diffstat (limited to 'drivers/net/can/dev/netlink.c')
| -rw-r--r-- | drivers/net/can/dev/netlink.c | 221 | 
1 files changed, 217 insertions, 4 deletions
| diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c index 80425636049d..95cca4e5251f 100644 --- a/drivers/net/can/dev/netlink.c +++ b/drivers/net/can/dev/netlink.c @@ -2,6 +2,7 @@  /* Copyright (C) 2005 Marc Kleine-Budde, Pengutronix   * Copyright (C) 2006 Andrey Volkov, Varma Electronics   * Copyright (C) 2008-2009 Wolfgang Grandegger <wg@grandegger.com> + * Copyright (C) 2021 Vincent Mailhol <mailhol.vincent@wanadoo.fr>   */  #include <linux/can/dev.h> @@ -19,6 +20,19 @@ static const struct nla_policy can_policy[IFLA_CAN_MAX + 1] = {  	[IFLA_CAN_DATA_BITTIMING] = { .len = sizeof(struct can_bittiming) },  	[IFLA_CAN_DATA_BITTIMING_CONST]	= { .len = sizeof(struct can_bittiming_const) },  	[IFLA_CAN_TERMINATION] = { .type = NLA_U16 }, +	[IFLA_CAN_TDC] = { .type = NLA_NESTED }, +}; + +static const struct nla_policy can_tdc_policy[IFLA_CAN_TDC_MAX + 1] = { +	[IFLA_CAN_TDC_TDCV_MIN] = { .type = NLA_U32 }, +	[IFLA_CAN_TDC_TDCV_MAX] = { .type = NLA_U32 }, +	[IFLA_CAN_TDC_TDCO_MIN] = { .type = NLA_U32 }, +	[IFLA_CAN_TDC_TDCO_MAX] = { .type = NLA_U32 }, +	[IFLA_CAN_TDC_TDCF_MIN] = { .type = NLA_U32 }, +	[IFLA_CAN_TDC_TDCF_MAX] = { .type = NLA_U32 }, +	[IFLA_CAN_TDC_TDCV] = { .type = NLA_U32 }, +	[IFLA_CAN_TDC_TDCO] = { .type = NLA_U32 }, +	[IFLA_CAN_TDC_TDCF] = { .type = NLA_U32 },  };  static int can_validate(struct nlattr *tb[], struct nlattr *data[], @@ -30,6 +44,7 @@ static int can_validate(struct nlattr *tb[], struct nlattr *data[],  	 * - nominal/arbitration bittiming  	 * - data bittiming  	 * - control mode with CAN_CTRLMODE_FD set +	 * - TDC parameters are coherent (details below)  	 */  	if (!data) @@ -37,8 +52,43 @@ static int can_validate(struct nlattr *tb[], struct nlattr *data[],  	if (data[IFLA_CAN_CTRLMODE]) {  		struct can_ctrlmode *cm = nla_data(data[IFLA_CAN_CTRLMODE]); +		u32 tdc_flags = cm->flags & CAN_CTRLMODE_TDC_MASK;  		is_can_fd = cm->flags & cm->mask & CAN_CTRLMODE_FD; + +		/* CAN_CTRLMODE_TDC_{AUTO,MANUAL} are mutually exclusive */ +		if (tdc_flags == CAN_CTRLMODE_TDC_MASK) +			return -EOPNOTSUPP; +		/* If one of the CAN_CTRLMODE_TDC_* flag is set then +		 * TDC must be set and vice-versa +		 */ +		if (!!tdc_flags != !!data[IFLA_CAN_TDC]) +			return -EOPNOTSUPP; +		/* If providing TDC parameters, at least TDCO is +		 * needed. TDCV is needed if and only if +		 * CAN_CTRLMODE_TDC_MANUAL is set +		 */ +		if (data[IFLA_CAN_TDC]) { +			struct nlattr *tb_tdc[IFLA_CAN_TDC_MAX + 1]; +			int err; + +			err = nla_parse_nested(tb_tdc, IFLA_CAN_TDC_MAX, +					       data[IFLA_CAN_TDC], +					       can_tdc_policy, extack); +			if (err) +				return err; + +			if (tb_tdc[IFLA_CAN_TDC_TDCV]) { +				if (tdc_flags & CAN_CTRLMODE_TDC_AUTO) +					return -EOPNOTSUPP; +			} else { +				if (tdc_flags & CAN_CTRLMODE_TDC_MANUAL) +					return -EOPNOTSUPP; +			} + +			if (!tb_tdc[IFLA_CAN_TDC_TDCO]) +				return -EOPNOTSUPP; +		}  	}  	if (is_can_fd) { @@ -46,7 +96,7 @@ static int can_validate(struct nlattr *tb[], struct nlattr *data[],  			return -EOPNOTSUPP;  	} -	if (data[IFLA_CAN_DATA_BITTIMING]) { +	if (data[IFLA_CAN_DATA_BITTIMING] || data[IFLA_CAN_TDC]) {  		if (!is_can_fd)  			return -EOPNOTSUPP;  	} @@ -54,11 +104,60 @@ static int can_validate(struct nlattr *tb[], struct nlattr *data[],  	return 0;  } +static int can_tdc_changelink(struct can_priv *priv, const struct nlattr *nla, +			      struct netlink_ext_ack *extack) +{ +	struct nlattr *tb_tdc[IFLA_CAN_TDC_MAX + 1]; +	struct can_tdc tdc = { 0 }; +	const struct can_tdc_const *tdc_const = priv->tdc_const; +	int err; + +	if (!tdc_const || !can_tdc_is_enabled(priv)) +		return -EOPNOTSUPP; + +	err = nla_parse_nested(tb_tdc, IFLA_CAN_TDC_MAX, nla, +			       can_tdc_policy, extack); +	if (err) +		return err; + +	if (tb_tdc[IFLA_CAN_TDC_TDCV]) { +		u32 tdcv = nla_get_u32(tb_tdc[IFLA_CAN_TDC_TDCV]); + +		if (tdcv < tdc_const->tdcv_min || tdcv > tdc_const->tdcv_max) +			return -EINVAL; + +		tdc.tdcv = tdcv; +	} + +	if (tb_tdc[IFLA_CAN_TDC_TDCO]) { +		u32 tdco = nla_get_u32(tb_tdc[IFLA_CAN_TDC_TDCO]); + +		if (tdco < tdc_const->tdco_min || tdco > tdc_const->tdco_max) +			return -EINVAL; + +		tdc.tdco = tdco; +	} + +	if (tb_tdc[IFLA_CAN_TDC_TDCF]) { +		u32 tdcf = nla_get_u32(tb_tdc[IFLA_CAN_TDC_TDCF]); + +		if (tdcf < tdc_const->tdcf_min || tdcf > tdc_const->tdcf_max) +			return -EINVAL; + +		tdc.tdcf = tdcf; +	} + +	priv->tdc = tdc; + +	return 0; +} +  static int can_changelink(struct net_device *dev, struct nlattr *tb[],  			  struct nlattr *data[],  			  struct netlink_ext_ack *extack)  {  	struct can_priv *priv = netdev_priv(dev); +	u32 tdc_mask = 0;  	int err;  	/* We need synchronization with dev->stop() */ @@ -138,7 +237,16 @@ static int can_changelink(struct net_device *dev, struct nlattr *tb[],  			dev->mtu = CAN_MTU;  			memset(&priv->data_bittiming, 0,  			       sizeof(priv->data_bittiming)); +			priv->ctrlmode &= ~CAN_CTRLMODE_TDC_MASK; +			memset(&priv->tdc, 0, sizeof(priv->tdc));  		} + +		tdc_mask = cm->mask & CAN_CTRLMODE_TDC_MASK; +		/* CAN_CTRLMODE_TDC_{AUTO,MANUAL} are mutually +		 * exclusive: make sure to turn the other one off +		 */ +		if (tdc_mask) +			priv->ctrlmode &= cm->flags | ~CAN_CTRLMODE_TDC_MASK;  	}  	if (data[IFLA_CAN_RESTART_MS]) { @@ -187,9 +295,26 @@ static int can_changelink(struct net_device *dev, struct nlattr *tb[],  			return -EINVAL;  		} -		memcpy(&priv->data_bittiming, &dbt, sizeof(dbt)); +		memset(&priv->tdc, 0, sizeof(priv->tdc)); +		if (data[IFLA_CAN_TDC]) { +			/* TDC parameters are provided: use them */ +			err = can_tdc_changelink(priv, data[IFLA_CAN_TDC], +						 extack); +			if (err) { +				priv->ctrlmode &= ~CAN_CTRLMODE_TDC_MASK; +				return err; +			} +		} else if (!tdc_mask) { +			/* Neither of TDC parameters nor TDC flags are +			 * provided: do calculation +			 */ +			can_calc_tdco(&priv->tdc, priv->tdc_const, &priv->data_bittiming, +				      &priv->ctrlmode, priv->ctrlmode_supported); +		} /* else: both CAN_CTRLMODE_TDC_{AUTO,MANUAL} are explicitly +		   * turned off. TDC is disabled: do nothing +		   */ -		can_calc_tdco(dev); +		memcpy(&priv->data_bittiming, &dbt, sizeof(dbt));  		if (priv->do_set_data_bittiming) {  			/* Finally, set the bit-timing registers */ @@ -226,6 +351,38 @@ static int can_changelink(struct net_device *dev, struct nlattr *tb[],  	return 0;  } +static size_t can_tdc_get_size(const struct net_device *dev) +{ +	struct can_priv *priv = netdev_priv(dev); +	size_t size; + +	if (!priv->tdc_const) +		return 0; + +	size = nla_total_size(0);			/* nest IFLA_CAN_TDC */ +	if (priv->ctrlmode_supported & CAN_CTRLMODE_TDC_MANUAL) { +		size += nla_total_size(sizeof(u32));	/* IFLA_CAN_TDCV_MIN */ +		size += nla_total_size(sizeof(u32));	/* IFLA_CAN_TDCV_MAX */ +	} +	size += nla_total_size(sizeof(u32));		/* IFLA_CAN_TDCO_MIN */ +	size += nla_total_size(sizeof(u32));		/* IFLA_CAN_TDCO_MAX */ +	if (priv->tdc_const->tdcf_max) { +		size += nla_total_size(sizeof(u32));	/* IFLA_CAN_TDCF_MIN */ +		size += nla_total_size(sizeof(u32));	/* IFLA_CAN_TDCF_MAX */ +	} + +	if (can_tdc_is_enabled(priv)) { +		if (priv->ctrlmode & CAN_CTRLMODE_TDC_MANUAL || +		    priv->do_get_auto_tdcv) +			size += nla_total_size(sizeof(u32));	/* IFLA_CAN_TDCV */ +		size += nla_total_size(sizeof(u32));		/* IFLA_CAN_TDCO */ +		if (priv->tdc_const->tdcf_max) +			size += nla_total_size(sizeof(u32));	/* IFLA_CAN_TDCF */ +	} + +	return size; +} +  static size_t can_get_size(const struct net_device *dev)  {  	struct can_priv *priv = netdev_priv(dev); @@ -257,10 +414,64 @@ static size_t can_get_size(const struct net_device *dev)  		size += nla_total_size(sizeof(*priv->data_bitrate_const) *  				       priv->data_bitrate_const_cnt);  	size += sizeof(priv->bitrate_max);			/* IFLA_CAN_BITRATE_MAX */ +	size += can_tdc_get_size(dev);				/* IFLA_CAN_TDC */  	return size;  } +static int can_tdc_fill_info(struct sk_buff *skb, const struct net_device *dev) +{ +	struct nlattr *nest; +	struct can_priv *priv = netdev_priv(dev); +	struct can_tdc *tdc = &priv->tdc; +	const struct can_tdc_const *tdc_const = priv->tdc_const; + +	if (!tdc_const) +		return 0; + +	nest = nla_nest_start(skb, IFLA_CAN_TDC); +	if (!nest) +		return -EMSGSIZE; + +	if (priv->ctrlmode_supported & CAN_CTRLMODE_TDC_MANUAL && +	    (nla_put_u32(skb, IFLA_CAN_TDC_TDCV_MIN, tdc_const->tdcv_min) || +	     nla_put_u32(skb, IFLA_CAN_TDC_TDCV_MAX, tdc_const->tdcv_max))) +		goto err_cancel; +	if (nla_put_u32(skb, IFLA_CAN_TDC_TDCO_MIN, tdc_const->tdco_min) || +	    nla_put_u32(skb, IFLA_CAN_TDC_TDCO_MAX, tdc_const->tdco_max)) +		goto err_cancel; +	if (tdc_const->tdcf_max && +	    (nla_put_u32(skb, IFLA_CAN_TDC_TDCF_MIN, tdc_const->tdcf_min) || +	     nla_put_u32(skb, IFLA_CAN_TDC_TDCF_MAX, tdc_const->tdcf_max))) +		goto err_cancel; + +	if (can_tdc_is_enabled(priv)) { +		u32 tdcv; +		int err = -EINVAL; + +		if (priv->ctrlmode & CAN_CTRLMODE_TDC_MANUAL) { +			tdcv = tdc->tdcv; +			err = 0; +		} else if (priv->do_get_auto_tdcv) { +			err = priv->do_get_auto_tdcv(dev, &tdcv); +		} +		if (!err && nla_put_u32(skb, IFLA_CAN_TDC_TDCV, tdcv)) +			goto err_cancel; +		if (nla_put_u32(skb, IFLA_CAN_TDC_TDCO, tdc->tdco)) +			goto err_cancel; +		if (tdc_const->tdcf_max && +		    nla_put_u32(skb, IFLA_CAN_TDC_TDCF, tdc->tdcf)) +			goto err_cancel; +	} + +	nla_nest_end(skb, nest); +	return 0; + +err_cancel: +	nla_nest_cancel(skb, nest); +	return -EMSGSIZE; +} +  static int can_fill_info(struct sk_buff *skb, const struct net_device *dev)  {  	struct can_priv *priv = netdev_priv(dev); @@ -318,7 +529,9 @@ static int can_fill_info(struct sk_buff *skb, const struct net_device *dev)  	    (nla_put(skb, IFLA_CAN_BITRATE_MAX,  		     sizeof(priv->bitrate_max), -		     &priv->bitrate_max)) +		     &priv->bitrate_max)) || + +	    (can_tdc_fill_info(skb, dev))  	    )  		return -EMSGSIZE; | 
