[PATCH v2 2/4] net: ax88796c: ASIX AX88796C SPI Ethernet Adapter Driver

Lukasz Stelmach l.stelmach at samsung.com
Tue Oct 13 16:04:52 EDT 2020


It was <2020-10-02 pią 22:36>, when Andrew Lunn wrote:
>> +static u32 ax88796c_get_link(struct net_device *ndev)
>> +{
>> +	struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
>> +
>> +	mutex_lock(&ax_local->spi_lock);
>> +
>> +	phy_read_status(ndev->phydev);
>> +
>> +	mutex_unlock(&ax_local->spi_lock);
>
> Why do you take this mutux before calling phy_read_status()? The
> phylib core will not be taking this mutex when it calls into the PHY
> driver. This applies to all the calls you have with phy_
>

I need to review the use of this mutex. Thanks for spotting.

> There should not be any need to call phy_read_status(). phylib will do
> this once per second, or after any interrupt from the PHY. so just use
>
>      phydev->link
>

Using ethtool_op_get_link()

>> +static void
>> +ax88796c_get_regs(struct net_device *ndev, struct ethtool_regs *regs, void *_p)
>> +{
>> +	struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
>> +	u16 *p = _p;
>> +	int offset, i;
>> +
>> +	memset(p, 0, AX88796C_REGDUMP_LEN);
>> +
>> +	for (offset = 0; offset < AX88796C_REGDUMP_LEN; offset += 2) {
>> +		if (!test_bit(offset / 2, ax88796c_no_regs_mask))
>> +			*p = AX_READ(&ax_local->ax_spi, offset);
>> +		p++;
>> +	}
>> +
>> +	for (i = 0; i < AX88796C_PHY_REGDUMP_LEN / 2; i++) {
>> +		*p = phy_read(ax_local->phydev, i);
>> +		p++;
>
> Depending on the PHY, that can be dangerous.

This is a built-in generic PHY. The chip has no lines to attach any
other external one.

> phylib could be busy doing things with the PHY. It could be looking at

How does phylib prevent concurrent access to a PHY? 

> a different page for example.

Different page? 

> miitool(1) can give you the same functionally without the MAC driver
> doing anything, other than forwarding the IOCTL call on.

No, I am afraid mii-tool is not able to dump registers. I am not insisting
on dumping PHY registeres but I think it is nice to have them. Intel
drivers do it.

>> +int ax88796c_mdio_read(struct mii_bus *mdiobus, int phy_id, int loc)
>> +{
>> +	struct ax88796c_device *ax_local = mdiobus->priv;
>> +	int ret;
>> +
>> +	AX_WRITE(&ax_local->ax_spi, MDIOCR_RADDR(loc)
>> +			| MDIOCR_FADDR(phy_id) | MDIOCR_READ, P2_MDIOCR);
>> +
>> +	ret = read_poll_timeout(AX_READ, ret,
>> +				(ret != 0),
>> +				0, jiffies_to_usecs(HZ / 100), false,
>> +				&ax_local->ax_spi, P2_MDIOCR);
>> +	if (ret)
>> +		return -EBUSY;
>
> Return whatever read_poll_timeout() returned. It is probably
> -ETIMEDOUT, but it could also be -EIO for example.

Indeed it is -ETIMEDOUT. Returning ret.

>> +ax88796c_mdio_write(struct mii_bus *mdiobus, int phy_id, int loc, u16 val)
>> +{
>> +	struct ax88796c_device *ax_local = mdiobus->priv;
>> +	int ret;
>> +
>> +	AX_WRITE(&ax_local->ax_spi, val, P2_MDIODR);
>> +
>> +	AX_WRITE(&ax_local->ax_spi,
>> +		 MDIOCR_RADDR(loc) | MDIOCR_FADDR(phy_id)
>> +		 | MDIOCR_WRITE, P2_MDIOCR);
>> +
>> +	ret = read_poll_timeout(AX_READ, ret,
>> +				((ret & MDIOCR_VALID) != 0), 0,
>> +				jiffies_to_usecs(HZ / 100), false,
>> +				&ax_local->ax_spi, P2_MDIOCR);
>> +	if (ret)
>> +		return -EIO;
>> +
>> +	if (loc == MII_ADVERTISE) {
>> +		AX_WRITE(&ax_local->ax_spi, (BMCR_FULLDPLX | BMCR_ANRESTART |
>> +			  BMCR_ANENABLE | BMCR_SPEED100), P2_MDIODR);
>> +		AX_WRITE(&ax_local->ax_spi, (MDIOCR_RADDR(MII_BMCR) |
>> +			  MDIOCR_FADDR(phy_id) | MDIOCR_WRITE),
>> +			  P2_MDIOCR);
>>
>
> What is this doing?
>

Well… it turns autonegotiation when changing advertised link modes. But
this is obvious. As to why this code is here, I will honestly say — I am
not sure (Reminder: this is a vendor driver I am porting, I am more than
happy to receive any comments, thank you). Apparently it is not required
and I am willing to remove it.  It could be of some use when the driver
didn't use phylib.

>> +		ret = read_poll_timeout(AX_READ, ret,
>> +					((ret & MDIOCR_VALID) != 0), 0,
>> +					jiffies_to_usecs(HZ / 100), false,
>> +					&ax_local->ax_spi, P2_MDIOCR);
>> +		if (ret)
>> +			return -EIO;
>> +	}
>> +
>> +	return 0;
>> +}
>
>> +static char *no_regs_list = "80018001,e1918001,8001a001,fc0d0000";
>> +unsigned long ax88796c_no_regs_mask[AX88796C_REGDUMP_LEN / (sizeof(unsigned long) * 8)];
>> +
>> +module_param(comp, int, 0444);
>> +MODULE_PARM_DESC(comp, "0=Non-Compression Mode, 1=Compression Mode");
>> +
>> +module_param(msg_enable, int, 0444);
>> +MODULE_PARM_DESC(msg_enable, "Message mask (see linux/netdevice.h for bitmap)");
>
> No module parameters allowed, not in netdev.
>
>> +static int ax88796c_reload_eeprom(struct ax88796c_device *ax_local)
>> +{
>> +	int ret;
>> +
>> +	AX_WRITE(&ax_local->ax_spi, EECR_RELOAD, P3_EECR);
>> +
>> +	ret = read_poll_timeout(AX_READ, ret,
>> +				(ret & PSR_DEV_READY),
>> +				0, jiffies_to_usecs(2 * HZ / 1000), false,
>> +				&ax_local->ax_spi, P0_PSR);
>> +	if (ret) {
>> +		dev_err(&ax_local->spi->dev,
>> +			"timeout waiting for reload eeprom\n");
>> +		return -1;
>
> return ret not EINVAL which is -1
>

Done.

>> +static int ax88796c_set_mac_address(struct net_device *ndev, void *p)
>> +{
>> +	struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
>> +	struct sockaddr *addr = p;
>> +
>> +	if (!is_valid_ether_addr(addr->sa_data))
>> +		return -EADDRNOTAVAIL;
>
> It would be better to just use eth_mac_addr().
>

Done.

>> +static int
>> +ax88796c_check_free_pages(struct ax88796c_device *ax_local, u8 need_pages)
>> +{
>> +	u8 free_pages;
>> +	u16 tmp;
>> +
>> +	free_pages = AX_READ(&ax_local->ax_spi, P0_TFBFCR) & TX_FREEBUF_MASK;
>> +	if (free_pages < need_pages) {
>> +		/* schedule free page interrupt */
>> +		tmp = AX_READ(&ax_local->ax_spi, P0_TFBFCR)
>> +				& TFBFCR_SCHE_FREE_PAGE;
>> +		AX_WRITE(&ax_local->ax_spi, tmp | TFBFCR_TX_PAGE_SET |
>> +				TFBFCR_SET_FREE_PAGE(need_pages),
>> +				P0_TFBFCR);
>> +		return -ENOMEM;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static struct sk_buff *
>> +ax88796c_tx_fixup(struct net_device *ndev, struct sk_buff_head *q)
>> +{
>> +	if (netif_msg_pktdata(ax_local)) {
>> +		char pfx[IFNAMSIZ + 7];
>> +
>> +		snprintf(pfx, sizeof(pfx), "%s:     ", ndev->name);
>> +
>> +		netdev_info(ndev, "TX packet len %d, total len %d, seq %d\n",
>> +			    pkt_len, tx_skb->len, seq_num);
>> +
>> +		netdev_info(ndev, "  SPI Header:\n");
>> +		print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1,
>> +			       tx_skb->data, 4, 0);
>> +
>> +		netdev_info(ndev, "  TX SOP:\n");
>> +		print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1,
>> +			       tx_skb->data + 4, TX_OVERHEAD, 0);
>> +
>> +		netdev_info(ndev, "  TX packet:\n");
>> +		print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1,
>> +			       tx_skb->data + 4 + TX_OVERHEAD,
>> +			       tx_skb->len - TX_EOP_SIZE - 4 - TX_OVERHEAD, 0);
>> +
>> +		netdev_info(ndev, "  TX EOP:\n");
>> +		print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1,
>> +			       tx_skb->data + tx_skb->len - 4, 4, 0);
>> +	}
>
> I expect others are going to ask you to remove this.
>

You mean dumping packets? I will if they do. What is pktdata flag for then?

>> +static void ax88796c_handle_link_change(struct net_device *ndev)
>> +{
>> +	if (net_ratelimit())
>> +		phy_print_status(ndev->phydev);
>> +}
>> +
>> +void ax88796c_phy_init(struct ax88796c_device *ax_local)
>> +{
>> +	/* Enable PHY auto-polling */
>> +	AX_WRITE(&ax_local->ax_spi,
>> +		 PCR_PHYID(0x10) | PCR_POLL_EN |
>> +		 PCR_POLL_FLOWCTRL | PCR_POLL_BMCR, P2_PCR);
>
> Auto-polling of the PHY is generally a bad idea. The hardware is not
> going to respect the phydev->lock mutex, for example. Disable this,
> and add a proper ax88796c_handle_link_change().
>

Done.

>> +static int
>> +ax88796c_open(struct net_device *ndev)
>> +{
>> +	struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
>> +	int ret;
>> +	unsigned long irq_flag = IRQF_SHARED;
>> +
>> +	mutex_lock(&ax_local->spi_lock);
>> +
>> +	ret = ax88796c_soft_reset(ax_local);
>> +	if (ret < 0)
>> +		return -ENODEV;
>> +
>> +	ret = request_irq(ndev->irq, ax88796c_interrupt,
>> +			  irq_flag, ndev->name, ndev);
>
> Maybe look at using request_threaded_irq(). You can then remove your
> work queue, and do the work in the thread_fn.
>

There is other work beeing done in the work queue too.

>> +	if (ret) {
>> +		netdev_err(ndev, "unable to get IRQ %d (errno=%d).\n",
>> +			   ndev->irq, ret);
>> +		return -ENXIO;
>
> return ret;
>
> In general, never change a return code unless you have a really good
> reason why. And if you do have a reason, document it.
>

OK, Done.

>> +static int
>> +ax88796c_close(struct net_device *ndev)
>> +{
>> +	struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
>> +
>> +	netif_stop_queue(ndev);
>> +
>> +	free_irq(ndev->irq, ndev);
>> +
>> +	phy_stop(ndev->phydev);
>> +
>> +	mutex_lock(&ax_local->spi_lock);
>> +
>> +	AX_WRITE(&ax_local->ax_spi, IMR_MASKALL, P0_IMR);
>> +	ax88796c_free_skb_queue(&ax_local->tx_wait_q);
>> +
>> +	ax88796c_soft_reset(ax_local);
>> +
>> +	mutex_unlock(&ax_local->spi_lock);
>> +	netif_carrier_off(ndev);
>
> phy_stop() will do that for you.
>

Removed.

>> +static int ax88796c_probe(struct spi_device *spi)
>> +{
>
>> +	ax_local->mdiobus->priv = ax_local;
>> +	ax_local->mdiobus->read = ax88796c_mdio_read;
>> +	ax_local->mdiobus->write = ax88796c_mdio_write;
>> +	ax_local->mdiobus->name = "ax88976c-mdiobus";
>> +	ax_local->mdiobus->phy_mask = ~(1 << 0x10);
>
> BIT(0x10);
>

Done.

>> +
>> +	ret = devm_register_netdev(&spi->dev, ndev);
>> +	if (ret) {
>> +		dev_err(&spi->dev, "failed to register a network device\n");
>> +		destroy_workqueue(ax_local->ax_work_queue);
>> +		goto err;
>> +	}
>
> The device is not live. If this is being used for NFS root, the kernel
> will start using it. So what sort of mess will it get into, if there
> is no PHY yet? Nothing important should happen after register_netdev().
>

But, with an unregistered network device ndev_owner in
phy_attach_direct() is NULL. Thus, phy_connect_direct() below fails.

--8<---------------cut here---------------start------------->8---
   1332         if (dev)
   1333                 ndev_owner = dev->dev.parent->driver->owner;
   1334         if (ndev_owner != bus->owner &&  !try_module_get(bus->owner)) {
   1335                 phydev_err(phydev, "failed to get the bus  module\n");
   1336                 return -EIO;
   1337         }
--8<---------------cut here---------------end--------------->8---


>> +
>> +	ax_local->phydev = phy_find_first(ax_local->mdiobus);
>> +	if (!ax_local->phydev) {
>> +		dev_err(&spi->dev, "no PHY found\n");
>> +		ret = -ENODEV;
>> +		goto err;
>> +	}
>> +
>> +	ax_local->phydev->irq = PHY_IGNORE_INTERRUPT;
>> +	phy_connect_direct(ax_local->ndev, ax_local->phydev,
>> +			   ax88796c_handle_link_change,
>> +			   PHY_INTERFACE_MODE_MII);
>> +
>> +	netif_info(ax_local, probe, ndev, "%s %s registered\n",
>> +		   dev_driver_string(&spi->dev),
>> +		   dev_name(&spi->dev));
>> +	phy_attached_info(ax_local->phydev);
>> +
>> +	ret = 0;
>> +err:
>> +	return ret;
>> +}
>> +
>> +static int ax88796c_remove(struct spi_device *spi)
>> +{
>> +	struct ax88796c_device *ax_local = dev_get_drvdata(&spi->dev);
>> +	struct net_device *ndev = ax_local->ndev;
>
> You might want to disconnect the PHY.
>

I do (-; Done.

>> +
>> +	netif_info(ax_local, probe, ndev, "removing network device %s %s\n",
>> +		   dev_driver_string(&spi->dev),
>> +		   dev_name(&spi->dev));
>> +
>> +	destroy_workqueue(ax_local->ax_work_queue);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id ax88796c_dt_ids[] = {
>> +	{ .compatible = "asix,ax88796c" },
>> +	{},
>> +};
>> +MODULE_DEVICE_TABLE(of, ax88796c_dt_ids);
>> +
>> +static const struct spi_device_id asix_id[] = {
>> +	{ "ax88796c", 0 },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(spi, asix_id);
>> +
>> +static struct spi_driver ax88796c_spi_driver = {
>> +	.driver = {
>> +		.name = DRV_NAME,
>> +#ifdef CONFIG_USE_OF
>> +		.of_match_table = of_match_ptr(ax88796c_dt_ids),
>> +#endif
>
> I don't think you need the #ifdef.
>

Indeed, it appears to be an uncommon practice to use it in this
context. Done.

>> +#ifndef _AX88796C_MAIN_H
>> +#define _AX88796C_MAIN_H
>> +
>> +#include <linux/netdevice.h>
>> +#include <linux/mii.h>
>> +
>> +#include "ax88796c_spi.h"
>> +
>> +/* These identify the driver base version and may not be removed. */
>> +#define DRV_NAME	"ax88796c"
>> +#define ADP_NAME	"ASIX AX88796C SPI Ethernet Adapter"
>> +#define DRV_VERSION	"1.2.0"
>
> DRV_VERSION are pretty pointless. Not sure you use it anyway. Please
> remove.
>

Done.

>> +	unsigned long		capabilities;
>> +		#define AX_CAP_DMA		1
>> +		#define AX_CAP_COMP		2
>> +		#define AX_CAP_BIDIR		4
>
> BIT(0), BIT(1), BIT(2)...
>

No problem. Do you have any recommendation how to express this

 #define PSR_RESET  (0 << 15)

I know it equals 0, but shows explicitly the bit number.

>> +struct skb_data;
>> +
>> +struct skb_data {
>> +	enum skb_state state;
>> +	struct net_device *ndev;
>> +	struct sk_buff *skb;
>> +	size_t len;
>> +	dma_addr_t phy_addr;
>> +};
>
> A forward definition, followed by the real definition?
>

There must have been something in between. Done.

>> +	#define FER_IPALM		(1 << 0)
>> +	#define FER_DCRC		(1 << 1)
>> +	#define FER_RH3M		(1 << 2)
>> +	#define FER_HEADERSWAP		(1 << 7)
>> +	#define FER_WSWAP		(1 << 8)
>> +	#define FER_BSWAP		(1 << 9)
>> +	#define FER_INTHI		(1 << 10)
>> +	#define FER_INTLO		(0 << 10)
>> +	#define FER_IRQ_PULL		(1 << 11)
>> +	#define FER_RXEN		(1 << 14)
>> +	#define FER_TXEN		(1 << 15)
>
> Isn't checkpatch giving warnings and suggesting BIT?

Not exactly. It gives green CHECK messages, which I decided to
ignore. Apparently a wrong move.

Thanks for the feedback.
-- 
Łukasz Stelmach
Samsung R&D Institute Poland
Samsung Electronics
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 487 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20201013/aab6b4d2/attachment.sig>


More information about the linux-arm-kernel mailing list