Re: [PATCH v8 3/3] net: ax88796c: ASIX AX88796C SPI Ethernet Adapter Driver

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

 



On Wed,  2 Dec 2020 22:47:09 +0100 Łukasz Stelmach wrote:
> ASIX AX88796[1] is a versatile ethernet adapter chip, that can be
> connected to a CPU with a 8/16-bit bus or with an SPI. This driver
> supports SPI connection.
> 
> The driver has been ported from the vendor kernel for ARTIK5[2]
> boards. Several changes were made to adapt it to the current kernel
> which include:
> 
> + updated DT configuration,
> + clock configuration moved to DT,
> + new timer, ethtool and gpio APIs,
> + dev_* instead of pr_* and custom printk() wrappers,
> + removed awkward vendor power managemtn.
> + introduced ethtool tunable to control SPI compression
> 
> [1] https://www.asix.com.tw/products.php?op=pItemdetail&PItemID=104;65;86&PLine=65
> [2] https://git.tizen.org/cgit/profile/common/platform/kernel/linux-3.10-artik/
> 
> The other ax88796 driver is for NE2000 compatible AX88796L chip. These
> chips are not compatible. Hence, two separate drivers are required.
> 
> Signed-off-by: Łukasz Stelmach <l.stelmach@xxxxxxxxxxx>
> Reviewed-by: Andrew Lunn <andrew@xxxxxxx>

> +	case ETHTOOL_SPI_COMPRESSION:
> +		if (netif_running(ndev))
> +			return -EBUSY;
> +		if ((*(u32 *)data) != 1)
> +			return -EINVAL;
> +		ax_local->capabilities &= ~AX_CAP_COMP;
> +		ax_local->capabilities |= (*(u32 *)data) == 1 ? AX_CAP_COMP : 0;

Since you're just using a single bit of information please use 
a private driver flag.

> +	headroom = skb_headroom(skb);
> +	tailroom = skb_tailroom(skb);
> +	padlen = ((pkt_len + 3) & 0x7FC) - pkt_len;

	round_up(pkt_len, 4) - pkt_len;

> +	tol_len = ((pkt_len + 3) & 0x7FC) +
> +			TX_OVERHEAD + TX_EOP_SIZE + spi_len;

Ditto

> +	seq_num = ++ax_local->seq_num & 0x1F;
> +
> +	info = (struct tx_pkt_info *)skb->cb;
> +	info->pkt_len = pkt_len;
> +
> +	if ((!skb_cloned(skb)) &&
> +	    (headroom >= (TX_OVERHEAD + spi_len)) &&
> +	    (tailroom >= (padlen + TX_EOP_SIZE))) {

> +	} else {

I think you can just use pskb_expand_head() instead of all this

> +		tx_skb = alloc_skb(tol_len, GFP_KERNEL);
> +		if (!tx_skb)
> +			return NULL;
> +
> +		/* Write SPI TXQ header */
> +		memcpy(skb_put(tx_skb, spi_len), ax88796c_tx_cmd_buf, spi_len);
> +
> +		info->seq_num = seq_num;
> +		ax88796c_proc_tx_hdr(info, skb->ip_summed);
> +
> +		/* SOP and SEG header */
> +		memcpy(skb_put(tx_skb, TX_OVERHEAD),
> +		       &info->sop, TX_OVERHEAD);
> +
> +		/* Packet */
> +		memcpy(skb_put(tx_skb, ((pkt_len + 3) & 0xFFFC)),
> +		       skb->data, pkt_len);
> +
> +		/* EOP header */
> +		memcpy(skb_put(tx_skb, TX_EOP_SIZE),
> +		       &info->eop, TX_EOP_SIZE);
> +
> +		skb_unlink(skb, q);
> +		dev_kfree_skb(skb);
> +	}

> +static void
> +ax88796c_skb_return(struct ax88796c_device *ax_local, struct sk_buff *skb,
> +		    struct rx_header *rxhdr)
> +{
> +	struct net_device *ndev = ax_local->ndev;
> +	int status;
> +
> +	do {
> +		if (!(ndev->features & NETIF_F_RXCSUM))
> +			break;
> +
> +		/* checksum error bit is set */
> +		if ((rxhdr->flags & RX_HDR3_L3_ERR) ||
> +		    (rxhdr->flags & RX_HDR3_L4_ERR))
> +			break;
> +
> +		/* Other types may be indicated by more than one bit. */
> +		if ((rxhdr->flags & RX_HDR3_L4_TYPE_TCP) ||
> +		    (rxhdr->flags & RX_HDR3_L4_TYPE_UDP))
> +			skb->ip_summed = CHECKSUM_UNNECESSARY;
> +	} while (0);
> +
> +	ax_local->stats.rx_packets++;
> +	ax_local->stats.rx_bytes += skb->len;
> +	skb->dev = ndev;
> +
> +	skb->truesize = skb->len + sizeof(struct sk_buff);

Why do you modify truesize?

> +	skb->protocol = eth_type_trans(skb, ax_local->ndev);
> +
> +	netif_info(ax_local, rx_status, ndev, "< rx, len %zu, type 0x%x\n",
> +		   skb->len + sizeof(struct ethhdr), skb->protocol);
> +
> +	status = netif_rx_ni(skb);
> +	if (status != NET_RX_SUCCESS)
> +		netif_info(ax_local, rx_err, ndev,
> +			   "netif_rx status %d\n", status);
> +}
> +
> +static void
> +ax88796c_rx_fixup(struct ax88796c_device *ax_local, struct sk_buff *rx_skb)
> +{
> +	struct rx_header *rxhdr = (struct rx_header *)rx_skb->data;
> +	struct net_device *ndev = ax_local->ndev;
> +	u16 len;
> +
> +	be16_to_cpus(&rxhdr->flags_len);
> +	be16_to_cpus(&rxhdr->seq_lenbar);
> +	be16_to_cpus(&rxhdr->flags);
> +
> +	if ((((short)rxhdr->flags_len) & RX_HDR1_PKT_LEN) !=
> +			 (~((short)rxhdr->seq_lenbar) & 0x7FF)) {

Why do you cast these values to signed types?
Is the casting necessary at all?

> +		netif_err(ax_local, rx_err, ndev, "Header error\n");
> +
> +		ax_local->stats.rx_frame_errors++;
> +		kfree_skb(rx_skb);
> +		return;
> +	}
> +
> +	if ((rxhdr->flags_len & RX_HDR1_MII_ERR) ||
> +	    (rxhdr->flags_len & RX_HDR1_CRC_ERR)) {
> +		netif_err(ax_local, rx_err, ndev, "CRC or MII error\n");
> +
> +		ax_local->stats.rx_crc_errors++;
> +		kfree_skb(rx_skb);
> +		return;
> +	}
> +
> +	len = rxhdr->flags_len & RX_HDR1_PKT_LEN;
> +	if (netif_msg_pktdata(ax_local)) {
> +		char pfx[IFNAMSIZ + 7];
> +
> +		snprintf(pfx, sizeof(pfx), "%s:     ", ndev->name);
> +		netdev_info(ndev, "RX data, total len %d, packet len %d\n",
> +			    rx_skb->len, len);
> +
> +		netdev_info(ndev, "  Dump RX packet header:");
> +		print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1,
> +			       rx_skb->data, sizeof(*rxhdr), 0);
> +
> +		netdev_info(ndev, "  Dump RX packet:");
> +		print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1,
> +			       rx_skb->data + sizeof(*rxhdr), len, 0);
> +	}
> +
> +	skb_pull(rx_skb, sizeof(*rxhdr));
> +	__pskb_trim(rx_skb, len);

Why __pskb_trim? skb_trim() should do.

> +	return ax88796c_skb_return(ax_local, rx_skb, rxhdr);

please drop the "return", both caller and callee are void.

> +}
> +
> +static int ax88796c_receive(struct net_device *ndev)
> +{
> +	struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
> +	struct skb_data *entry;
> +	u16 w_count, pkt_len;
> +	struct sk_buff *skb;
> +	u8 pkt_cnt;
> +
> +	WARN_ON(!mutex_is_locked(&ax_local->spi_lock));
> +
> +	/* check rx packet and total word count */
> +	AX_WRITE(&ax_local->ax_spi, AX_READ(&ax_local->ax_spi, P0_RTWCR)
> +		  | RTWCR_RX_LATCH, P0_RTWCR);
> +
> +	pkt_cnt = AX_READ(&ax_local->ax_spi, P0_RXBCR2) & RXBCR2_PKT_MASK;
> +	if (!pkt_cnt)
> +		return 0;
> +
> +	pkt_len = AX_READ(&ax_local->ax_spi, P0_RCPHR) & 0x7FF;
> +
> +	w_count = ((pkt_len + 6 + 3) & 0xFFFC) >> 1;
> +
> +	skb = alloc_skb((w_count * 2), GFP_ATOMIC);

netdev_alloc_skb()

> +	if (!skb) {
> +		AX_WRITE(&ax_local->ax_spi, RXBCR1_RXB_DISCARD, P0_RXBCR1);
> +		return 0;
> +	}
> +	entry = (struct skb_data *)skb->cb;
> +
> +	AX_WRITE(&ax_local->ax_spi, RXBCR1_RXB_START | w_count, P0_RXBCR1);
> +
> +	axspi_read_rxq(&ax_local->ax_spi,
> +		       skb_put(skb, w_count * 2), skb->len);
> +
> +	/* Check if rx bridge is idle */
> +	if ((AX_READ(&ax_local->ax_spi, P0_RXBCR2) & RXBCR2_RXB_IDLE) == 0) {
> +		netif_err(ax_local, rx_err, ndev,
> +			  "Rx Bridge is not idle\n");
> +		AX_WRITE(&ax_local->ax_spi, RXBCR2_RXB_REINIT, P0_RXBCR2);
> +
> +		entry->state = rx_err;
> +	} else {
> +		entry->state = rx_done;
> +	}
> +
> +	AX_WRITE(&ax_local->ax_spi, ISR_RXPKT, P0_ISR);
> +
> +	ax88796c_rx_fixup(ax_local, skb);
> +
> +	return 1;
> +}

> +static irqreturn_t ax88796c_interrupt(int irq, void *dev_instance)
> +{
> +	struct ax88796c_device *ax_local;
> +	struct net_device *ndev;
> +
> +	ndev = dev_instance;
> +	if (!ndev) {
> +		pr_err("irq %d for unknown device.\n", irq);
> +		return IRQ_RETVAL(0);
> +	}
> +	ax_local = to_ax88796c_device(ndev);
> +
> +	disable_irq_nosync(irq);
> +
> +	netif_dbg(ax_local, intr, ndev, "Interrupt occurred\n");
> +
> +	set_bit(EVENT_INTR, &ax_local->flags);
> +	schedule_work(&ax_local->ax_work);
> +
> +	return IRQ_HANDLED;
> +}

Since you always punt to a workqueue did you consider just using
threaded interrupts instead? 

Also you're not checking if this is actually your devices IRQ that
triggered. You can't set IRQF_SHARED.

> +static int
> +ax88796c_open(struct net_device *ndev)
> +{
> +	struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
> +	unsigned long irq_flag = IRQF_SHARED;
> +	int fc = AX_FC_NONE;
> +	int ret;
> +
> +	ret = request_irq(ndev->irq, ax88796c_interrupt,
> +			  irq_flag, ndev->name, ndev);
> +	if (ret) {
> +		netdev_err(ndev, "unable to get IRQ %d (errno=%d).\n",
> +			   ndev->irq, ret);
> +		return ret;
> +	}
> +
> +	mutex_lock(&ax_local->spi_lock);
> +
> +	ret = ax88796c_soft_reset(ax_local);
> +	if (ret < 0) {
> +		mutex_unlock(&ax_local->spi_lock);
> +		return ret;

What frees the IRQ?

> +	}
> +	ax_local->seq_num = 0x1f;
> +
> +	ax88796c_set_mac_addr(ndev);
> +	ax88796c_set_csums(ax_local);
> +
> +	/* Disable stuffing packet */
> +	AX_WRITE(&ax_local->ax_spi,
> +		 AX_READ(&ax_local->ax_spi, P1_RXBSPCR)
> +		 & ~RXBSPCR_STUF_ENABLE, P1_RXBSPCR);

Please use a temporary variable or create a RMW helper.

> +	/* Enable RX packet process */
> +	AX_WRITE(&ax_local->ax_spi, RPPER_RXEN, P1_RPPER);
> +
> +	AX_WRITE(&ax_local->ax_spi, AX_READ(&ax_local->ax_spi, P0_FER)
> +		 | FER_RXEN | FER_TXEN | FER_BSWAP | FER_IRQ_PULL, P0_FER);

Ditto. etc.

> +	ndev = devm_alloc_etherdev(&spi->dev, sizeof(*ax_local));
> +	if (!ndev)
> +		return -ENOMEM;
> +
> +	SET_NETDEV_DEV(ndev, &spi->dev);
> +
> +	ax_local = to_ax88796c_device(ndev);
> +	memset(ax_local, 0, sizeof(*ax_local));

No need to zero out netdev priv, it's zalloced.

> +	mutex_lock(&ax_local->spi_lock);
> +
> +	/* ax88796c gpio reset */
> +	ax88796c_hard_reset(ax_local);
> +
> +	/* Reset AX88796C */
> +	ret = ax88796c_soft_reset(ax_local);
> +	if (ret < 0) {
> +		ret = -ENODEV;
> +		goto err;
> +	}
> +	/* Check board revision */
> +	temp = AX_READ(&ax_local->ax_spi, P2_CRIR);
> +	if ((temp & 0xF) != 0x0) {
> +		dev_err(&spi->dev, "spi read failed: %d\n", temp);
> +		ret = -ENODEV;
> +		goto err;
> +	}

These jump out without releasing the lock.

> +	/* Disable power saving */
> +	AX_WRITE(&ax_local->ax_spi, (AX_READ(&ax_local->ax_spi, P0_PSCR)
> +				     & PSCR_PS_MASK) | PSCR_PS_D0, P0_PSCR);

This is asking for a temporary variable or a RMW helper.

> +	u8			plat_endian;
> +		#define PLAT_LITTLE_ENDIAN	0
> +		#define PLAT_BIG_ENDIAN		1

Why do you store this little nugget of information?

> +/* Tx headers structure */
> +struct tx_sop_header {
> +	/* bit 15-11: flags, bit 10-0: packet length */
> +	u16 flags_len;
> +	/* bit 15-11: sequence number, bit 11-0: packet length bar */
> +	u16 seq_lenbar;
> +} __packed;
> +
> +struct tx_segment_header {
> +	/* bit 15-14: flags, bit 13-11: segment number */
> +	/* bit 10-0: segment length */
> +	u16 flags_seqnum_seglen;
> +	/* bit 15-14: end offset, bit 13-11: start offset */
> +	/* bit 10-0: segment length bar */
> +	u16 eo_so_seglenbar;
> +} __packed;
> +
> +struct tx_eop_header {
> +	/* bit 15-11: sequence number, bit 10-0: packet length */
> +	u16 seq_len;
> +	/* bit 15-11: sequence number bar, bit 10-0: packet length bar */
> +	u16 seqbar_lenbar;
> +} __packed;
> +
> +struct tx_pkt_info {
> +	struct tx_sop_header sop;
> +	struct tx_segment_header seg;
> +	struct tx_eop_header eop;
> +	u16 pkt_len;
> +	u16 seq_num;
> +} __packed;
> +
> +/* Rx headers structure */
> +struct rx_header {
> +	u16 flags_len;
> +	u16 seq_lenbar;
> +	u16 flags;
> +} __packed;

These all look like multiple of 2 bytes. Why do they need to be packed?

> +u16 axspi_read_reg(struct axspi_data *ax_spi, u8 reg)
> +{
> +	int ret;
> +	int len = ax_spi->comp ? 3 : 4;
> +
> +	ax_spi->cmd_buf[0] = 0x03;	/* OP code read register */
> +	ax_spi->cmd_buf[1] = reg;	/* register address */
> +	ax_spi->cmd_buf[2] = 0xFF;	/* dumy cycle */
> +	ax_spi->cmd_buf[3] = 0xFF;	/* dumy cycle */
> +	ret = spi_write_then_read(ax_spi->spi,
> +				  ax_spi->cmd_buf, len,
> +				  ax_spi->rx_buf, 2);
> +	if (ret)
> +		dev_err(&ax_spi->spi->dev, "%s() failed: ret = %d\n", __func__, ret);
> +	else
> +		le16_to_cpus(ax_spi->rx_buf);
> +
> +	return *(u16 *)ax_spi->rx_buf;

No need to return some specific pattern on failure? Like 0xffff?




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux