Re: [PATCH, RFC] ems_usb: Added support for EMS CPC-USB/ARM7 CAN/USB interface

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Am Samstag, 12. September 2009 12:28:40 schrieb Sebastian Haas:
> The following patch adds a driver to support the CAN\USB interface
> CPC-USB/ARM7. The patch was already posted on netdev for inclusion, but as
> the driver is also a USB driver it would be nice if someone of the Linux
> USB folks could take a look at the USB side of the driver. Thanks.
>

I put comments among the code. I hope you find them useful.

	Regards
		Oliver

> +
> +/*
> + * callback for bulk IN urb
> + */
> +static void ems_usb_write_bulk_callback(struct urb *urb)
> +{
> +	struct ems_tx_urb_context *context = urb->context;
> +	struct ems_usb *dev;
> +	struct net_device *netdev;
> +
> +	if (!context)
> +		return;
Can this happen?
> +
> +	dev = context->dev;
> +	netdev = dev->netdev;
> +
> +	if (!netif_device_present(netdev))
> +		return;

You don't free the buffer in the error case. Memory leak?
> +
> +	if (urb->status)
> +		dev_info(netdev->dev.parent, "Tx URB aborted (%d)\n",
> +			 urb->status);
> +
> +	/* free up our allocated buffer */
> +	usb_buffer_free(urb->dev, urb->transfer_buffer_length,
> +			urb->transfer_buffer, urb->transfer_dma);
> +
> +	netdev->trans_start = jiffies;
> +
> +	/* transmission complete interrupt */
> +	netdev->stats.tx_packets++;
> +	netdev->stats.tx_bytes += context->dlc;
> +
> +	can_get_echo_skb(netdev, context->echo_index);
> +
> +	/* Release context */
> +	context->echo_index = MAX_TX_URBS;
> +
> +	atomic_dec(&dev->active_tx_urbs);
> +
> +	if (netif_queue_stopped(netdev))
> +		netif_wake_queue(netdev);
> +}
> +
[..]
> +
> +/*
> + * Start interface
> + */
> +static int ems_usb_start(struct ems_usb *dev)
> +{
> +	struct net_device *netdev = dev->netdev;
> +	int err, i;
> +
> +	dev->intr_in_buffer[0] = 0;
> +	dev->free_slots = 15; /* initial size */
> +
> +	for (i = 0; i < MAX_RX_URBS; i++) {
> +		struct urb *urb = NULL;
> +		u8 *buf = NULL;
> +
> +		/* create a URB, and a buffer for it */
> +		urb = usb_alloc_urb(0, GFP_ATOMIC);

Why GFP_ATOMIC?

> +		if (!urb) {
> +			dev_err(netdev->dev.parent,
> +				"No memory left for URBs\n");
> +			return -ENOMEM;
> +		}
> +
> +		buf = usb_buffer_alloc(dev->udev, RX_BUFFER_SIZE, GFP_KERNEL,
> +				       &urb->transfer_dma);
> +		if (!buf) {
> +			dev_err(netdev->dev.parent,
> +				"No memory left for USB buffer\n");
> +			usb_free_urb(urb);
> +			return -ENOMEM;
> +		}
> +
> +		usb_fill_bulk_urb(urb, dev->udev, usb_rcvbulkpipe(dev->udev, 2),
> +				  buf, RX_BUFFER_SIZE,
> +				  ems_usb_read_bulk_callback, dev);
> +		urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> +		usb_anchor_urb(urb, &dev->rx_submitted);
> +
> +		err = usb_submit_urb(urb, GFP_KERNEL);
> +		if (err) {
> +			if (err == -ENODEV)
> +				netif_device_detach(dev->netdev);
> +
> +			usb_unanchor_urb(urb);
> +			usb_buffer_free(dev->udev, RX_BUFFER_SIZE, buf,
> +					urb->transfer_dma);
> +			break;
> +		}
> +
> +		/* Drop reference, USB core will take care of freeing it */
> +		usb_free_urb(urb);
> +	}
> +
> +	/* Did we submit any URBs */
> +	if (i == 0) {
> +		dev_warn(netdev->dev.parent, "couldn't setup read URBs\n");
> +		return err;
> +	}
> +
> +	/* Warn if we've couldn't transmit all the URBs */
> +	if (i < MAX_RX_URBS)
> +		dev_warn(netdev->dev.parent, "rx performance may be slow\n");
> +
> +	/* Setup and start interrupt URB */
> +	usb_fill_int_urb(dev->intr_urb, dev->udev,
> +			 usb_rcvintpipe(dev->udev, 1),
> +			 dev->intr_in_buffer,

This is a DMA coherency bug. You need to allocate a separate buffer.

> +			 sizeof(dev->intr_in_buffer),
> +			 ems_usb_read_interrupt_callback, dev, 1);
> +
> +	err = usb_submit_urb(dev->intr_urb, GFP_KERNEL);
> +	if (err) {
> +		if (err == -ENODEV)
> +			netif_device_detach(dev->netdev);
> +
> +		dev_warn(netdev->dev.parent, "intr URB submit failed: %d\n",
> +			 err);
> +
> +		return err;
> +	}
> +
> +	/* CPC-USB will transfer received message to host */
> +	err = ems_usb_control_cmd(dev, CONTR_CAN_MESSAGE | CONTR_CONT_ON);
> +	if (err)
> +		goto failed;
> +
> +	/* CPC-USB will transfer CAN state changes to host */
> +	err = ems_usb_control_cmd(dev, CONTR_CAN_STATE | CONTR_CONT_ON);
> +	if (err)
> +		goto failed;
> +
> +	/* CPC-USB will transfer bus errors to host */
> +	err = ems_usb_control_cmd(dev, CONTR_BUS_ERROR | CONTR_CONT_ON);
> +	if (err)
> +		goto failed;
> +
> +	err = ems_usb_write_mode(dev, SJA1000_MOD_NORMAL);
> +	if (err)
> +		goto failed;
> +
> +	dev->can.state = CAN_STATE_ERROR_ACTIVE;
> +
> +	return 0;
> +
> +failed:
> +	if (err == -ENODEV)
> +		netif_device_detach(dev->netdev);
> +
> +	dev_warn(netdev->dev.parent, "couldn't submit control: %d\n", err);
> +
> +	return err;
> +}
[..]
> +
> +static int ems_usb_start_xmit(struct sk_buff *skb, struct net_device
> *netdev) +{
> +	struct ems_usb *dev = netdev_priv(netdev);
> +	struct ems_tx_urb_context *context = NULL;
> +	struct net_device_stats *stats = &netdev->stats;
> +	struct can_frame *cf = (struct can_frame *)skb->data;
> +	struct ems_cpc_msg *msg;
> +	struct urb *urb;
> +	u8 *buf;
> +	int i, err;
> +	size_t size = CPC_HEADER_SIZE + CPC_MSG_HEADER_LEN
> +			+ sizeof(struct cpc_can_msg);
> +
> +	/* create a URB, and a buffer for it, and copy the data to the URB */
> +	urb = usb_alloc_urb(0, GFP_ATOMIC);

GFP_ATOMIC

> +	if (!urb) {
> +		dev_err(netdev->dev.parent, "No memory left for URBs\n");
> +		goto nomem;
> +	}
> +
> +	buf = usb_buffer_alloc(dev->udev, size, GFP_KERNEL, &urb->transfer_dma);

GFP_KERNEL. Only one of these flags can be right.

> +	if (!buf) {
> +		dev_err(netdev->dev.parent, "No memory left for USB buffer\n");
> +		usb_free_urb(urb);
> +		goto nomem;
> +	}
> +
> +	msg = (struct ems_cpc_msg *)&buf[CPC_HEADER_SIZE];
> +
> +	msg->msg.can_msg.id = cf->can_id & 0x1FFFFFFFU;
> +	msg->msg.can_msg.length = cf->can_dlc;
> +
> +	if (cf->can_id & CAN_RTR_FLAG) {
> +		msg->type = cf->can_id & CAN_EFF_FLAG ?
> +			CPC_CMD_TYPE_EXT_RTR_FRAME : CPC_CMD_TYPE_RTR_FRAME;
> +
> +		msg->length = CPC_CAN_MSG_MIN_SIZE;
> +	} else {
> +		msg->type = cf->can_id & CAN_EFF_FLAG ?
> +			CPC_CMD_TYPE_EXT_CAN_FRAME : CPC_CMD_TYPE_CAN_FRAME;
> +
> +		for (i = 0; i < cf->can_dlc; i++)
> +			msg->msg.can_msg.msg[i] = cf->data[i];
> +
> +		msg->length = CPC_CAN_MSG_MIN_SIZE + cf->can_dlc;
> +	}
> +
> +	for (i = 0; i < MAX_TX_URBS; i++) {
> +		if (dev->tx_contexts[i].echo_index == MAX_TX_URBS) {
> +			context = &dev->tx_contexts[i];
> +			break;
> +		}
> +	}
> +
> +	/*
> +	 * May never happen! When this happens we'd more URBs in flight as
> +	 * allowed (MAX_TX_URBS).
> +	 */
> +	if (!context) {
> +		usb_unanchor_urb(urb);
> +		usb_buffer_free(dev->udev, size, buf, urb->transfer_dma);
> +
> +		dev_warn(netdev->dev.parent, "couldn't find free context\n");
> +
> +		return NETDEV_TX_BUSY;
> +	}
> +
> +	context->dev = dev;
> +	context->echo_index = i;
> +	context->dlc = cf->can_dlc;
> +
> +	usb_fill_bulk_urb(urb, dev->udev, usb_sndbulkpipe(dev->udev, 2), buf,
> +			  size, ems_usb_write_bulk_callback, context);
> +	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> +	usb_anchor_urb(urb, &dev->tx_submitted);
> +
> +	can_put_echo_skb(skb, netdev, context->echo_index);
> +
> +	err = usb_submit_urb(urb, GFP_ATOMIC);
> +	if (unlikely(err)) {
> +		can_free_echo_skb(netdev, context->echo_index);
> +
> +		usb_unanchor_urb(urb);
> +		usb_buffer_free(dev->udev, size, buf, urb->transfer_dma);
> +		dev_kfree_skb(skb);
> +
> +		if (err == -ENODEV) {
> +			netif_device_detach(netdev);
> +		} else {
> +			dev_warn(netdev->dev.parent, "failed tx_urb %d\n", err);
> +
> +			stats->tx_dropped++;
> +		}
> +	} else {
> +		atomic_inc(&dev->active_tx_urbs);

The URB may have finished, thus dropping active_tx_urbs below zero.

> +
> +		netdev->trans_start = jiffies;
> +
> +		/* Slow down tx path */
> +		if (atomic_read(&dev->active_tx_urbs) >= MAX_TX_URBS ||
> +		    dev->free_slots < 5) {
> +			netif_stop_queue(netdev);

You don't call usb_free_urb()

> +			return NETDEV_TX_OK;

I'd simply drop the return.

> +		}
> +	}
> +
> +	/*
> +	 * Release our reference to this URB, the USB core will eventually free
> +	 * it entirely.
> +	 */
> +	usb_free_urb(urb);
> +
> +	return NETDEV_TX_OK;
> +
> +nomem:
> +	if (skb)
> +		dev_kfree_skb(skb);
> +
> +	stats->tx_dropped++;
> +
> +	return NETDEV_TX_OK;
> +}
> +
> +static int ems_usb_close(struct net_device *netdev)
> +{
> +	struct ems_usb *dev = netdev_priv(netdev);
> +
> +	netif_stop_queue(netdev);
> +
> +	/* Stop polling */
> +	unlink_all_urbs(dev);

Looks like a race. A completing urb can call netif_wake_queue()

> +	/* Set CAN controller to reset mode */
> +	if (ems_usb_write_mode(dev, SJA1000_MOD_RM))
> +		dev_warn(netdev->dev.parent, "couldn't stop device");
> +
> +	close_candev(netdev);
> +
> +	dev->open_time = 0;
> +
> +	return 0;
> +}

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux