forked from mirrors/linux
		
	can: kvaser_usb_leaf: Fix improved state not being reported
The tested 0bfd:0017 Kvaser Memorator Professional HS/HS FW 2.0.50 and
0bfd:0124 Kvaser Mini PCI Express 2xHS FW 4.18.778 do not seem to send
any unsolicited events when error counters decrease or when the device
transitions from ERROR_PASSIVE to ERROR_ACTIVE (or WARNING).
This causes the interface to e.g. indefinitely stay in the ERROR_PASSIVE
state.
Fix that by asking for chip state (inc. counters) event every 0.5 secs
when error counters are non-zero.
Since there are non-error-counter devices, also always poll in
ERROR_PASSIVE even if the counters show zero.
Fixes: 080f40a6fa ("can: kvaser_usb: Add support for Kvaser CAN/USB devices")
Tested-by: Jimmy Assarsson <extja@kvaser.com>
Signed-off-by: Anssi Hannula <anssi.hannula@bitwise.fi>
Signed-off-by: Jimmy Assarsson <extja@kvaser.com>
Link: https://lore.kernel.org/all/20221010185237.319219-7-extja@kvaser.com
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
			
			
This commit is contained in:
		
							parent
							
								
									df1b7af276
								
							
						
					
					
						commit
						8d21f5927a
					
				
					 3 changed files with 81 additions and 3 deletions
				
			
		|  | @ -104,6 +104,9 @@ struct kvaser_usb_net_priv { | |||
| 	struct can_priv can; | ||||
| 	struct can_berr_counter bec; | ||||
| 
 | ||||
| 	/* subdriver-specific data */ | ||||
| 	void *sub_priv; | ||||
| 
 | ||||
| 	struct kvaser_usb *dev; | ||||
| 	struct net_device *netdev; | ||||
| 	int channel; | ||||
|  | @ -125,6 +128,8 @@ struct kvaser_usb_net_priv { | |||
|  * | ||||
|  * @dev_setup_endpoints:	setup USB in and out endpoints | ||||
|  * @dev_init_card:		initialize card | ||||
|  * @dev_init_channel:		initialize channel | ||||
|  * @dev_remove_channel:		uninitialize channel | ||||
|  * @dev_get_software_info:	get software info | ||||
|  * @dev_get_software_details:	get software details | ||||
|  * @dev_get_card_info:		get card info | ||||
|  | @ -146,6 +151,8 @@ struct kvaser_usb_dev_ops { | |||
| 				    struct can_berr_counter *bec); | ||||
| 	int (*dev_setup_endpoints)(struct kvaser_usb *dev); | ||||
| 	int (*dev_init_card)(struct kvaser_usb *dev); | ||||
| 	int (*dev_init_channel)(struct kvaser_usb_net_priv *priv); | ||||
| 	void (*dev_remove_channel)(struct kvaser_usb_net_priv *priv); | ||||
| 	int (*dev_get_software_info)(struct kvaser_usb *dev); | ||||
| 	int (*dev_get_software_details)(struct kvaser_usb *dev); | ||||
| 	int (*dev_get_card_info)(struct kvaser_usb *dev); | ||||
|  |  | |||
|  | @ -684,6 +684,7 @@ static const struct ethtool_ops kvaser_usb_ethtool_ops_hwts = { | |||
| 
 | ||||
| static void kvaser_usb_remove_interfaces(struct kvaser_usb *dev) | ||||
| { | ||||
| 	const struct kvaser_usb_dev_ops *ops = dev->driver_info->ops; | ||||
| 	int i; | ||||
| 
 | ||||
| 	for (i = 0; i < dev->nchannels; i++) { | ||||
|  | @ -699,6 +700,9 @@ static void kvaser_usb_remove_interfaces(struct kvaser_usb *dev) | |||
| 		if (!dev->nets[i]) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (ops->dev_remove_channel) | ||||
| 			ops->dev_remove_channel(dev->nets[i]); | ||||
| 
 | ||||
| 		free_candev(dev->nets[i]->netdev); | ||||
| 	} | ||||
| } | ||||
|  | @ -772,17 +776,26 @@ static int kvaser_usb_init_one(struct kvaser_usb *dev, int channel) | |||
| 
 | ||||
| 	dev->nets[channel] = priv; | ||||
| 
 | ||||
| 	if (ops->dev_init_channel) { | ||||
| 		err = ops->dev_init_channel(priv); | ||||
| 		if (err) | ||||
| 			goto err; | ||||
| 	} | ||||
| 
 | ||||
| 	err = register_candev(netdev); | ||||
| 	if (err) { | ||||
| 		dev_err(&dev->intf->dev, "Failed to register CAN device\n"); | ||||
| 		free_candev(netdev); | ||||
| 		dev->nets[channel] = NULL; | ||||
| 		return err; | ||||
| 		goto err; | ||||
| 	} | ||||
| 
 | ||||
| 	netdev_dbg(netdev, "device registered\n"); | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| err: | ||||
| 	free_candev(netdev); | ||||
| 	dev->nets[channel] = NULL; | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| static int kvaser_usb_probe(struct usb_interface *intf, | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ | |||
| #include <linux/types.h> | ||||
| #include <linux/units.h> | ||||
| #include <linux/usb.h> | ||||
| #include <linux/workqueue.h> | ||||
| 
 | ||||
| #include <linux/can.h> | ||||
| #include <linux/can/dev.h> | ||||
|  | @ -56,6 +57,7 @@ | |||
| #define CMD_RX_EXT_MESSAGE		14 | ||||
| #define CMD_TX_EXT_MESSAGE		15 | ||||
| #define CMD_SET_BUS_PARAMS		16 | ||||
| #define CMD_GET_CHIP_STATE		19 | ||||
| #define CMD_CHIP_STATE_EVENT		20 | ||||
| #define CMD_SET_CTRL_MODE		21 | ||||
| #define CMD_RESET_CHIP			24 | ||||
|  | @ -421,6 +423,12 @@ struct kvaser_usb_err_summary { | |||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| struct kvaser_usb_net_leaf_priv { | ||||
| 	struct kvaser_usb_net_priv *net; | ||||
| 
 | ||||
| 	struct delayed_work chip_state_req_work; | ||||
| }; | ||||
| 
 | ||||
| static const struct can_bittiming_const kvaser_usb_leaf_m16c_bittiming_const = { | ||||
| 	.name = "kvaser_usb_ucii", | ||||
| 	.tseg1_min = 4, | ||||
|  | @ -943,6 +951,16 @@ static int kvaser_usb_leaf_simple_cmd_async(struct kvaser_usb_net_priv *priv, | |||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| static void kvaser_usb_leaf_chip_state_req_work(struct work_struct *work) | ||||
| { | ||||
| 	struct kvaser_usb_net_leaf_priv *leaf = | ||||
| 		container_of(work, struct kvaser_usb_net_leaf_priv, | ||||
| 			     chip_state_req_work.work); | ||||
| 	struct kvaser_usb_net_priv *priv = leaf->net; | ||||
| 
 | ||||
| 	kvaser_usb_leaf_simple_cmd_async(priv, CMD_GET_CHIP_STATE); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| kvaser_usb_leaf_rx_error_update_can_state(struct kvaser_usb_net_priv *priv, | ||||
| 					const struct kvaser_usb_err_summary *es, | ||||
|  | @ -1014,6 +1032,7 @@ static void kvaser_usb_leaf_rx_error(const struct kvaser_usb *dev, | |||
| 	struct sk_buff *skb; | ||||
| 	struct net_device_stats *stats; | ||||
| 	struct kvaser_usb_net_priv *priv; | ||||
| 	struct kvaser_usb_net_leaf_priv *leaf; | ||||
| 	enum can_state old_state, new_state; | ||||
| 
 | ||||
| 	if (es->channel >= dev->nchannels) { | ||||
|  | @ -1023,6 +1042,7 @@ static void kvaser_usb_leaf_rx_error(const struct kvaser_usb *dev, | |||
| 	} | ||||
| 
 | ||||
| 	priv = dev->nets[es->channel]; | ||||
| 	leaf = priv->sub_priv; | ||||
| 	stats = &priv->netdev->stats; | ||||
| 
 | ||||
| 	/* Update all of the CAN interface's state and error counters before
 | ||||
|  | @ -1039,6 +1059,14 @@ static void kvaser_usb_leaf_rx_error(const struct kvaser_usb *dev, | |||
| 	kvaser_usb_leaf_rx_error_update_can_state(priv, es, &tmp_cf); | ||||
| 	new_state = priv->can.state; | ||||
| 
 | ||||
| 	/* If there are errors, request status updates periodically as we do
 | ||||
| 	 * not get automatic notifications of improved state. | ||||
| 	 */ | ||||
| 	if (new_state < CAN_STATE_BUS_OFF && | ||||
| 	    (es->rxerr || es->txerr || new_state == CAN_STATE_ERROR_PASSIVE)) | ||||
| 		schedule_delayed_work(&leaf->chip_state_req_work, | ||||
| 				      msecs_to_jiffies(500)); | ||||
| 
 | ||||
| 	skb = alloc_can_err_skb(priv->netdev, &cf); | ||||
| 	if (!skb) { | ||||
| 		stats->rx_dropped++; | ||||
|  | @ -1573,10 +1601,13 @@ static int kvaser_usb_leaf_start_chip(struct kvaser_usb_net_priv *priv) | |||
| 
 | ||||
| static int kvaser_usb_leaf_stop_chip(struct kvaser_usb_net_priv *priv) | ||||
| { | ||||
| 	struct kvaser_usb_net_leaf_priv *leaf = priv->sub_priv; | ||||
| 	int err; | ||||
| 
 | ||||
| 	init_completion(&priv->stop_comp); | ||||
| 
 | ||||
| 	cancel_delayed_work(&leaf->chip_state_req_work); | ||||
| 
 | ||||
| 	err = kvaser_usb_leaf_send_simple_cmd(priv->dev, CMD_STOP_CHIP, | ||||
| 					      priv->channel); | ||||
| 	if (err) | ||||
|  | @ -1623,6 +1654,31 @@ static int kvaser_usb_leaf_init_card(struct kvaser_usb *dev) | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int kvaser_usb_leaf_init_channel(struct kvaser_usb_net_priv *priv) | ||||
| { | ||||
| 	struct kvaser_usb_net_leaf_priv *leaf; | ||||
| 
 | ||||
| 	leaf = devm_kzalloc(&priv->dev->intf->dev, sizeof(*leaf), GFP_KERNEL); | ||||
| 	if (!leaf) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	leaf->net = priv; | ||||
| 	INIT_DELAYED_WORK(&leaf->chip_state_req_work, | ||||
| 			  kvaser_usb_leaf_chip_state_req_work); | ||||
| 
 | ||||
| 	priv->sub_priv = leaf; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void kvaser_usb_leaf_remove_channel(struct kvaser_usb_net_priv *priv) | ||||
| { | ||||
| 	struct kvaser_usb_net_leaf_priv *leaf = priv->sub_priv; | ||||
| 
 | ||||
| 	if (leaf) | ||||
| 		cancel_delayed_work_sync(&leaf->chip_state_req_work); | ||||
| } | ||||
| 
 | ||||
| static int kvaser_usb_leaf_set_bittiming(struct net_device *netdev) | ||||
| { | ||||
| 	struct kvaser_usb_net_priv *priv = netdev_priv(netdev); | ||||
|  | @ -1720,6 +1776,8 @@ const struct kvaser_usb_dev_ops kvaser_usb_leaf_dev_ops = { | |||
| 	.dev_get_berr_counter = kvaser_usb_leaf_get_berr_counter, | ||||
| 	.dev_setup_endpoints = kvaser_usb_leaf_setup_endpoints, | ||||
| 	.dev_init_card = kvaser_usb_leaf_init_card, | ||||
| 	.dev_init_channel = kvaser_usb_leaf_init_channel, | ||||
| 	.dev_remove_channel = kvaser_usb_leaf_remove_channel, | ||||
| 	.dev_get_software_info = kvaser_usb_leaf_get_software_info, | ||||
| 	.dev_get_software_details = NULL, | ||||
| 	.dev_get_card_info = kvaser_usb_leaf_get_card_info, | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Anssi Hannula
						Anssi Hannula