2233 lines
66 KiB
C
2233 lines
66 KiB
C
/****************************************************************************
|
|
* drivers/net/w5500.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* References:
|
|
* [W5500] W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <debug.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/wdog.h>
|
|
#include <nuttx/wqueue.h>
|
|
#include <nuttx/net/arp.h>
|
|
#include <nuttx/net/netdev.h>
|
|
#include <nuttx/net/w5500.h>
|
|
#include <nuttx/signal.h>
|
|
|
|
#ifdef CONFIG_NET_PKT
|
|
# include <nuttx/net/pkt.h>
|
|
#endif
|
|
|
|
#ifdef CONFIG_NET_W5500
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/* Work queue support is required. */
|
|
|
|
#if !defined(CONFIG_SCHED_WORKQUEUE)
|
|
# error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
|
|
#else
|
|
|
|
/* The low priority work queue is preferred. If it is not enabled, LPWORK
|
|
* will be the same as HPWORK.
|
|
*
|
|
* NOTE: However, the network should NEVER run on the high priority work
|
|
* queue! That queue is intended only to service short back end interrupt
|
|
* processing that never suspends. Suspending the high priority work queue
|
|
* may bring the system to its knees!
|
|
*/
|
|
|
|
#define ETHWORK LPWORK
|
|
|
|
/* CONFIG_NET_W5500_NINTERFACES determines the number of physical interfaces
|
|
* that will be supported.
|
|
*/
|
|
|
|
#ifndef CONFIG_NET_W5500_NINTERFACES
|
|
# define CONFIG_NET_W5500_NINTERFACES 1
|
|
#endif
|
|
|
|
/* TX timeout = 1 minute
|
|
* CLK_TCK is the number of clock ticks per second
|
|
*/
|
|
|
|
#define W5500_TXTIMEOUT (60 * CLK_TCK)
|
|
|
|
/* This is a helper pointer for accessing the contents of Ethernet header */
|
|
|
|
#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
|
|
|
|
/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
|
|
* Tx RAM. A maximum size is conservatively assumed per Ethernet frame in
|
|
* order to simplify buffer management logic.
|
|
*/
|
|
|
|
#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
|
|
|
|
/* W5500 SPI Host Interface *************************************************/
|
|
|
|
#define W5500_BSB_COMMON_REGS 0x00u
|
|
#define W5500_BSB_SOCKET_REGS(n) (((n & 0x7u) << 5) | 0x08u)
|
|
#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
|
|
#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
|
|
|
|
#define W5500_RWB_READ 0x00u
|
|
#define W5500_RWB_WRITE 0x04u
|
|
|
|
#define W5500_OM_DATA_LEN_VAR 0x00u
|
|
#define W5500_OM_DATA_LEN_1 0x01u
|
|
#define W5500_OM_DATA_LEN_2 0x02u
|
|
#define W5500_OM_DATA_LEN_4 0x03u
|
|
|
|
/* W5500 Register Addresses *************************************************/
|
|
|
|
/* Common Register Block */
|
|
|
|
#define W5500_MR 0x0000 /* Mode */
|
|
#define W5500_GAR0 0x0001 /* Gateway Address */
|
|
#define W5500_GAR1 0x0002
|
|
#define W5500_GAR2 0x0003
|
|
#define W5500_GAR3 0x0004
|
|
#define W5500_SUBR0 0x0005 /* Subnet Mask Address */
|
|
#define W5500_SUBR1 0x0006
|
|
#define W5500_SUBR2 0x0007
|
|
#define W5500_SUBR3 0x0008
|
|
#define W5500_SHAR0 0x0009 /* Source Hardware Address */
|
|
#define W5500_SHAR1 0x000a
|
|
#define W5500_SHAR2 0x000b
|
|
#define W5500_SHAR3 0x000c
|
|
#define W5500_SHAR4 0x000d
|
|
#define W5500_SHAR5 0x000e
|
|
#define W5500_SIPR0 0x000f /* Source IP Address */
|
|
#define W5500_SIPR1 0x0010
|
|
#define W5500_SIPR2 0x0011
|
|
#define W5500_SIPR3 0x0012
|
|
#define W5500_INTLEVEL0 0x0013 /* Interrupt Low Level Timer */
|
|
#define W5500_INTLEVEL1 0x0014
|
|
#define W5500_IR 0x0015 /* Interrupt */
|
|
#define W5500_IMR 0x0016 /* Interrupt Mask */
|
|
#define W5500_SIR 0x0017 /* Socket Interrupt */
|
|
#define W5500_SIMR 0x0018 /* Socket Interrupt Mask */
|
|
#define W5500_RTR0 0x0019 /* Retry Time */
|
|
#define W5500_RTR1 0x001a
|
|
#define W5500_RCR 0x001b /* Retry Count */
|
|
#define W5500_PTIMER 0x001c /* PPP LCP Request Timer */
|
|
#define W5500_PMAGIC 0x001d /* PPP LCP Magic number */
|
|
#define W5500_PHAR0 0x001e /* PPP Destination MAC Address */
|
|
#define W5500_PHAR1 0x001f
|
|
#define W5500_PHAR2 0x0020
|
|
#define W5500_PHAR3 0x0021
|
|
#define W5500_PHAR4 0x0022
|
|
#define W5500_PHAR5 0x0023
|
|
#define W5500_PSID0 0x0024 /* PPP Session Identification */
|
|
#define W5500_PSID1 0x0025
|
|
#define W5500_PMRU0 0x0026 /* PPP Maximum Segment Size */
|
|
#define W5500_PMRU1 0x0027
|
|
#define W5500_UIPR0 0x0028 /* Unreachable IP address */
|
|
#define W5500_UIPR1 0x0029
|
|
#define W5500_UIPR2 0x002a
|
|
#define W5500_UIPR3 0x002b
|
|
#define W5500_UPORTR0 0x002c /* Unreachable Port */
|
|
#define W5500_UPORTR1 0x002d
|
|
#define W5500_PHYCFGR 0x002e /* PHY Configuration */
|
|
/* 0x002f-0x0038: Reserved */
|
|
#define W5500_VERSIONR 0x0039 /* Chip version */
|
|
/* 0x003a-0xffff: Reserved */
|
|
|
|
/* Socket Register Block */
|
|
|
|
#define W5500_SN_MR 0x0000 /* Socket n Mode */
|
|
#define W5500_SN_CR 0x0001 /* Socket n Command */
|
|
#define W5500_SN_IR 0x0002 /* Socket n Interrupt */
|
|
#define W5500_SN_SR 0x0003 /* Socket n Status */
|
|
#define W5500_SN_PORT0 0x0004 /* Socket n Source Port */
|
|
#define W5500_SN_PORT1 0x0005
|
|
#define W5500_SN_DHAR0 0x0006 /* Socket n Destination Hardware Address */
|
|
#define W5500_SN_DHAR1 0x0007
|
|
#define W5500_SN_DHAR2 0x0008
|
|
#define W5500_SN_DHAR3 0x0009
|
|
#define W5500_SN_DHAR4 0x000a
|
|
#define W5500_SN_DHAR5 0x000b
|
|
#define W5500_SN_DIPR0 0x000c /* Socket n Destination IP Address */
|
|
#define W5500_SN_DIPR1 0x000d
|
|
#define W5500_SN_DIPR2 0x000e
|
|
#define W5500_SN_DIPR3 0x000f
|
|
#define W5500_SN_DPORT0 0x0010 /* Socket n Destination Port */
|
|
#define W5500_SN_DPORT1 0x0011
|
|
#define W5500_SN_MSSR0 0x0012 /* Socket n Maximum Segment Size */
|
|
#define W5500_SN_MSSR1 0x0013
|
|
/* 0x0014: Reserved */
|
|
#define W5500_SN_TOS 0x0015 /* Socket n IP TOS */
|
|
#define W5500_SN_TTL 0x0016 /* Socket n IP TTL */
|
|
/* 0x0017-0x001d: Reserved */
|
|
#define W5500_SN_RXBUF_SIZE 0x001e /* Socket n Receive Buffer Size */
|
|
#define W5500_SN_TXBUF_SIZE 0x001f /* Socket n Transmit Buffer Size */
|
|
#define W5500_SN_TX_FSR0 0x0020 /* Socket n TX Free Size */
|
|
#define W5500_SN_TX_FSR1 0x0021
|
|
#define W5500_SN_TX_RD0 0x0022 /* Socket n TX Read Pointer */
|
|
#define W5500_SN_TX_RD1 0x0023
|
|
#define W5500_SN_TX_WR0 0x0024 /* Socket n TX Write Pointer */
|
|
#define W5500_SN_TX_WR1 0x0025
|
|
#define W5500_SN_RX_RSR0 0x0026 /* Socket n RX Received Size */
|
|
#define W5500_SN_RX_RSR1 0x0027
|
|
#define W5500_SN_RX_RD0 0x0028 /* Socket n RX Read Pointer */
|
|
#define W5500_SN_RX_RD1 0x0029
|
|
#define W5500_SN_RX_WR0 0x002a /* Socket n RX Write Pointer */
|
|
#define W5500_SN_RX_WR1 0x002b
|
|
#define W5500_SN_IMR 0x002c /* Socket n Interrupt Mask */
|
|
#define W5500_SN_FRAG0 0x002d /* Socket n Fragment Offset in IP header */
|
|
#define W5500_SN_FRAG1 0x002e
|
|
#define W5500_SN_KPALVTR 0x002f /* Keep alive timer */
|
|
/* 0x0030-0xffff: Reserved */
|
|
|
|
/* W5500 Register Bitfield Definitions **************************************/
|
|
|
|
/* Common Register Block */
|
|
|
|
/* Mode Register (MR) */
|
|
|
|
#define MR_FARP (1 << 1) /* Bit 1: Force ARP */
|
|
#define MR_PPPOE (1 << 3) /* Bit 3: PPPoE Mode */
|
|
#define MR_PB (1 << 4) /* Bit 4: Ping Block Mode */
|
|
#define MR_WOL (1 << 5) /* Bit 5: Wake on LAN */
|
|
#define MR_RST (1 << 7) /* Bit 7: Reset registers */
|
|
|
|
/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
|
|
|
|
#define INT_MP (1 << 4) /* Bit 4: Magic Packet */
|
|
#define INT_PPPOE (1 << 5) /* Bit 5: PPPoE Connection Close */
|
|
#define INT_UNREACH (1 << 6) /* Bit 6: Destination unreachable */
|
|
#define INT_CONFLICT (1 << 7) /* Bit 7: IP Conflict */
|
|
|
|
/* Socket Interrupt Register (SIR) */
|
|
|
|
#define SIR(n) (1 << (n))
|
|
|
|
/* Socket Interrupt Mask Register (SIMR)) */
|
|
|
|
#define SIMR(n) (1 << (n))
|
|
|
|
/* PHY Configuration Register (PHYCFGR) */
|
|
|
|
#define PHYCFGR_LNK (1 << 0) /* Bit 0: Link Status */
|
|
#define PHYCFGR_SPD (1 << 1) /* Bit 1: Speed Status */
|
|
#define PHYCFGR_DPX (1 << 2) /* Bit 2: Duplex Status */
|
|
#define PHYCFGR_OPMDC_SHIFT (3) /* Bits 3-5: Operation Mode Configuration */
|
|
#define PHYCFGR_OPMDC_MASK (7 << PHYCFGR_OPMDC_SHIFT)
|
|
# define PHYCFGR_OPMDC_10BT_HD_NAN (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
|
|
# define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
|
|
# define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
|
|
# define PHYCFGR_OPMDC_10BT_FD_NAN (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
|
|
* Auto-negotiation */
|
|
# define PHYCFGR_OPMDC_100BT_HD_AN (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
|
|
* Auto-negotiation */
|
|
# define PHYCFGR_OPMDC_POWER_DOWN (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
|
|
# define PHYCFGR_OPMDC_ALLCAP_AN (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
|
|
* Auto-negotiation */
|
|
|
|
#define PHYCFGR_OPMD (1 << 6) /* Bit 6: Configure PHY Operation Mode */
|
|
#define PHYCFGR_RST (1 << 7) /* Bit 7: Reset */
|
|
|
|
/* Socket Register Block */
|
|
|
|
/* Socket n Mode Register (SN_MR) */
|
|
|
|
#define SN_MR_PROTOCOL_SHIFT (0) /* Bits 0-3: Protocol */
|
|
#define SN_MR_PROTOCOL_MASK (15 << SN_MR_PROTOCOL_SHIFT)
|
|
# define SN_MR_P0 (1 << (SN_MR_PROTOCOL_SHIFT + 0))
|
|
# define SN_MR_P1 (1 << (SN_MR_PROTOCOL_SHIFT + 1))
|
|
# define SN_MR_P2 (1 << (SN_MR_PROTOCOL_SHIFT + 2))
|
|
# define SN_MR_P3 (1 << (SN_MR_PROTOCOL_SHIFT + 3))
|
|
# define SM_MR_CLOSED 0
|
|
# define SM_MR_TCP SN_MR_P0
|
|
# define SM_MR_UDP SN_MR_P1
|
|
# define SM_MR_MACRAW SN_MR_P2
|
|
#define SN_MR_UCASTB (1 << 4) /* Bit 4: UNICAST Blocking in UDP mode */
|
|
#define SN_MR_MIP6B (1 << 4) /* Bit 4: IPv6 packet Blocking in MACRAW mode */
|
|
#define SN_MR_ND (1 << 5) /* Bit 5: Use No Delayed ACK */
|
|
#define SN_MR_MC (1 << 5) /* Bit 5: Multicast */
|
|
#define SN_MR_MMB (1 << 5) /* Bit 5: Multicast Blocking in MACRAW mode */
|
|
#define SN_MR_BCASTB (1 << 6) /* Bit 6: Broadcast Blocking in MACRAW and
|
|
* UDP mode */
|
|
#define SN_MR_MULTI (1 << 7) /* Bit 7: Multicasting in UDP mode */
|
|
#define SN_MR_MFEN (1 << 7) /* Bit 7: MAC Filter Enable in MACRAW mode */
|
|
|
|
/* Socket n Command Register (SN_CR) */
|
|
|
|
#define SN_CR_OPEN 0x01 /* Socket n is initialized and opened according
|
|
* to the protocol selected in SN_MR */
|
|
#define SN_CR_LISTEN 0x02 /* Socket n operates as a 'TCP server' and waits
|
|
* for connection request from any 'TCP client' */
|
|
#define SN_CR_CONNECT 0x04 /* 'TCP client' connection request */
|
|
#define SN_CR_DISCON 0x08 /* TCP disconnection request */
|
|
#define SN_CR_CLOSE 0x10 /* Close socket n */
|
|
#define SN_CR_SEND 0x20 /* Transmit all data in Socket n TX buffer */
|
|
#define SN_CR_SEND_MAC 0x21 /* Transmit all UDP data (no ARP) */
|
|
#define SN_CR_SEND_KEEP 0x22 /* Send TCP keep-alive packet */
|
|
#define SN_CR_RECV 0x40 /* Complete received data in Socket n RX buffer */
|
|
|
|
/* Socket n Interrupt Register (SN_IR) and
|
|
* Socket n Interrupt Mask Register (SN_IMR)
|
|
*/
|
|
|
|
#define SN_INT_CON (1 << 0) /* Bit 0: Connection with peer successful */
|
|
#define SN_INT_DISCON (1 << 1) /* Bit 1: FIN or FIN/ACK received from peer */
|
|
#define SN_INT_RECV (1 << 2) /* Bit 2: Data received from peer */
|
|
#define SN_INT_TIMEOUT (1 << 3) /* Bit 3: ARP or TCP timeout */
|
|
#define SN_INT_SEND_OK (1 << 4) /* Bit 4: SEND command completed */
|
|
|
|
/* Socket n Status Register (SN_SR) */
|
|
|
|
#define SN_SR_SOCK_CLOSED 0x00
|
|
#define SN_SR_SOCK_INIT 0x13
|
|
#define SN_SR_SOCK_LISTEN 0x14
|
|
#define SN_SR_SOCK_ESTABLISHED 0x17
|
|
#define SN_SR_SOCK_CLOSE_WAIT 0x1c
|
|
#define SN_SR_SOCK_UDP 0x22
|
|
#define SN_SR_SOCK_MACRAW 0x42
|
|
|
|
#define SN_SR_SOCK_SYNSENT 0x15 /* Transitional status */
|
|
#define SN_SR_SOCK_SYNRECV 0x16
|
|
#define SN_SR_SOCK_FIN_WAIT 0x18
|
|
#define SN_SR_SOCK_CLOSING 0x1a
|
|
#define SN_SR_SOCK_TIME_WAIT 0x1b
|
|
#define SN_SR_SOCK_LAST_ACK 0x1d
|
|
|
|
/* Socket n RX Buffer Size Register (SN_RXBUF) */
|
|
|
|
#define SN_RXBUF_0KB 0
|
|
#define SN_RXBUF_1KB 1
|
|
#define SN_RXBUF_2KB 2
|
|
#define SN_RXBUF_4KB 4
|
|
#define SN_RXBUF_8KB 5
|
|
#define SN_RXBUF_16KB 16
|
|
|
|
/* Socket n TX Buffer Size Register (SN_TXBUF) */
|
|
|
|
#define SN_TXBUF_0KB 0
|
|
#define SN_TXBUF_1KB 1
|
|
#define SN_TXBUF_2KB 2
|
|
#define SN_TXBUF_4KB 4
|
|
#define SN_TXBUF_8KB 5
|
|
#define SN_TXBUF_16KB 16
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* The w5500_driver_s encapsulates all state information for a single
|
|
* hardware interface
|
|
*/
|
|
|
|
struct w5500_driver_s
|
|
{
|
|
bool sk_bifup; /* true:ifup false:ifdown */
|
|
struct wdog_s sk_txtimeout; /* TX timeout timer */
|
|
struct work_s sk_irqwork; /* For deferring interrupt work to the work queue */
|
|
struct work_s sk_pollwork; /* For deferring poll work to the work queue */
|
|
|
|
/* Ethernet frame transmission buffer management */
|
|
|
|
uint16_t txbuf_offset[NUM_TXBUFS + 1];
|
|
uint8_t txbuf_rdptr;
|
|
uint8_t txbuf_wrptr;
|
|
|
|
/* The hardware interconnect to the W5500 chip */
|
|
|
|
FAR struct spi_dev_s *spi_dev; /* SPI hardware access */
|
|
FAR const struct w5500_lower_s *lower; /* Low-level MCU specific */
|
|
|
|
/* This holds the information visible to the NuttX network */
|
|
|
|
struct net_driver_s sk_dev; /* Interface understood by the network */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
/* These statically allocated structures would mean that only a single
|
|
* instance of the device could be supported. In order to support multiple
|
|
* devices instances, this data would have to be allocated dynamically.
|
|
*/
|
|
|
|
/* A single packet buffer per device is used in this example. There might
|
|
* be multiple packet buffers in a more complex, pipelined design. Many
|
|
* contemporary Ethernet interfaces, for example, use multiple, linked DMA
|
|
* descriptors in rings to implement such a pipeline. This example assumes
|
|
* much simpler hardware that simply handles one packet at a time.
|
|
*
|
|
* NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
|
|
* need a minimum on one packet buffer per instance. Much better to be
|
|
* allocated dynamically in cases where more than one are needed.
|
|
*/
|
|
|
|
static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
|
|
|
|
/* Driver state structure */
|
|
|
|
static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Common TX logic */
|
|
|
|
static void w5500_transmit(FAR struct w5500_driver_s *priv);
|
|
static int w5500_txpoll(FAR struct net_driver_s *dev);
|
|
|
|
/* Interrupt handling */
|
|
|
|
static void w5500_reply(struct w5500_driver_s *priv);
|
|
static void w5500_receive(FAR struct w5500_driver_s *priv);
|
|
static void w5500_txdone(FAR struct w5500_driver_s *priv);
|
|
|
|
static void w5500_interrupt_work(FAR void *arg);
|
|
static int w5500_interrupt(int irq, FAR void *context, FAR void *arg);
|
|
|
|
/* Watchdog timer expirations */
|
|
|
|
static void w5500_txtimeout_work(FAR void *arg);
|
|
static void w5500_txtimeout_expiry(wdparm_t arg);
|
|
|
|
/* NuttX callback functions */
|
|
|
|
static int w5500_ifup(FAR struct net_driver_s *dev);
|
|
static int w5500_ifdown(FAR struct net_driver_s *dev);
|
|
|
|
static void w5500_txavail_work(FAR void *arg);
|
|
static int w5500_txavail(FAR struct net_driver_s *dev);
|
|
|
|
#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
|
|
static int w5500_addmac(FAR struct net_driver_s *dev,
|
|
FAR const uint8_t *mac);
|
|
#ifdef CONFIG_NET_MCASTGROUP
|
|
static int w5500_rmmac(FAR struct net_driver_s *dev,
|
|
FAR const uint8_t *mac);
|
|
#endif
|
|
#ifdef CONFIG_NET_ICMPv6
|
|
static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
|
|
#endif
|
|
#endif
|
|
#ifdef CONFIG_NETDEV_IOCTL
|
|
static int w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
|
|
unsigned long arg);
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/* Hardware interface to W5500 **********************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_reset
|
|
*
|
|
* Description:
|
|
* Apply the hardware reset sequence to the W5500 (See [W5500], section
|
|
* 5.5.1 Reset Timing). Optinonally, keep hardware reset asserted.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
* keep - Whether the hardware reset shall be left asserted.
|
|
*
|
|
* Assumptions:
|
|
* This function must only be called in interrupt context, if argument
|
|
* keep is set to false (otherwise it sleeps, which is not allowed in
|
|
* interrupt context).
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
|
|
{
|
|
self->lower->reset(self->lower, true);
|
|
|
|
if (!keep)
|
|
{
|
|
nxsig_usleep(500); /* [W5500]: T_RC (Reset Cycle Time) min 500 us */
|
|
|
|
self->lower->reset(self->lower, false);
|
|
|
|
nxsig_usleep(1000); /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_lock
|
|
*
|
|
* Description:
|
|
* Acquire exclusive access to the W5500's SPI bus and configure it
|
|
* accordingly.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_lock(FAR struct w5500_driver_s *self)
|
|
{
|
|
int ret;
|
|
|
|
ret = SPI_LOCK(self->spi_dev, true);
|
|
DEBUGASSERT(ret == OK);
|
|
|
|
SPI_SETMODE(self->spi_dev, self->lower->mode);
|
|
SPI_SETBITS(self->spi_dev, 8);
|
|
SPI_HWFEATURES(self->spi_dev, 0);
|
|
SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
|
|
SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_unlock
|
|
*
|
|
* Description:
|
|
* Relinquish exclusive access to the W5500's SPI bus
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_unlock(FAR struct w5500_driver_s *self)
|
|
{
|
|
int ret;
|
|
|
|
SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
|
|
ret = SPI_LOCK(self->spi_dev, false);
|
|
DEBUGASSERT(ret == OK);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_read
|
|
*
|
|
* Description:
|
|
* Read a number of bytes from one of the W5500's register or buffer
|
|
* blocks.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
* block_select_bits - Register or buffer block to read from
|
|
* offset - The offset address within the block
|
|
* buffer - Buffer to copy read data into
|
|
* len - Number of bytes to read from block
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_read(FAR struct w5500_driver_s *self,
|
|
uint8_t block_select_bits,
|
|
uint16_t offset,
|
|
void *buffer,
|
|
uint16_t len)
|
|
{
|
|
uint8_t addr_cntl[3];
|
|
|
|
addr_cntl[0] = (uint8_t)(offset >> 8);
|
|
addr_cntl[1] = (uint8_t)offset;
|
|
addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
|
|
|
|
w5500_lock(self);
|
|
SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
|
|
SPI_RECVBLOCK(self->spi_dev, buffer, len);
|
|
w5500_unlock(self);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_write
|
|
*
|
|
* Description:
|
|
* Write a number of bytes to one of the W5500's register or buffer
|
|
* blocks.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
* block_select_bits - Register or buffer block to read from
|
|
* offset - The offset address within the block
|
|
* Data - Data to write to block
|
|
* len - Number of bytes to write to block
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_write(FAR struct w5500_driver_s *self,
|
|
uint8_t block_select_bits,
|
|
off_t offset,
|
|
const void *data,
|
|
size_t len)
|
|
{
|
|
uint8_t addr_cntl[3];
|
|
|
|
addr_cntl[0] = (uint8_t)(offset >> 8);
|
|
addr_cntl[1] = (uint8_t)offset;
|
|
addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
|
|
|
|
w5500_lock(self);
|
|
SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
|
|
SPI_SNDBLOCK(self->spi_dev, data, len);
|
|
w5500_unlock(self);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_read8
|
|
*
|
|
* Description:
|
|
* Read a single byte from one of the W5500's register or buffer blocks.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
* block_select_bits - Register or buffer block to read from
|
|
* offset - The offset address within the block
|
|
*
|
|
* Returned Value:
|
|
* The byte read
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
|
|
uint8_t block_select_bits,
|
|
uint16_t offset)
|
|
{
|
|
uint8_t value;
|
|
|
|
w5500_read(self,
|
|
block_select_bits,
|
|
offset,
|
|
&value,
|
|
sizeof(value));
|
|
|
|
return value;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_write8
|
|
*
|
|
* Description:
|
|
* Write a single byte to one of the W5500's register or buffer blocks.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
* block_select_bits - Register or buffer block to read from
|
|
* offset - The offset address within the block
|
|
* value - The byte value to write
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_write8(FAR struct w5500_driver_s *self,
|
|
uint8_t block_select_bits,
|
|
uint16_t offset,
|
|
uint8_t value)
|
|
{
|
|
w5500_write(self,
|
|
block_select_bits,
|
|
offset,
|
|
&value,
|
|
sizeof(value));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_read16
|
|
*
|
|
* Description:
|
|
* Read a two byte value from one of the W5500's register or buffer blocks.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
* block_select_bits - Register or buffer block to read from
|
|
* offset - The offset address within the block
|
|
*
|
|
* Returned Value:
|
|
* The two byte value read in host byte order.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
|
|
uint8_t block_select_bits,
|
|
uint16_t offset)
|
|
{
|
|
uint16_t value;
|
|
|
|
w5500_read(self,
|
|
block_select_bits,
|
|
offset,
|
|
&value,
|
|
sizeof(value));
|
|
|
|
return NTOHS(value);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_write16
|
|
*
|
|
* Description:
|
|
* Write a two byte value to one of the W5500's register or buffer blocks.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
* block_select_bits - Register or buffer block to read from
|
|
* offset - The offset address within the block
|
|
* value - The two byte value to write in host byte order.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_write16(FAR struct w5500_driver_s *self,
|
|
uint8_t block_select_bits,
|
|
uint16_t offset,
|
|
uint16_t value)
|
|
{
|
|
value = HTONS(value);
|
|
|
|
w5500_write(self,
|
|
block_select_bits,
|
|
offset,
|
|
&value,
|
|
sizeof(value));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_read16_atomic
|
|
*
|
|
* Description:
|
|
* Read a two-byte value that is concurrently updated by the W5500 hardware
|
|
* in a safe fashion. In [W5500] it is recommended to read the 16 bits
|
|
* multiple times until one gets the same value twice in a row.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
* block_select_bits - Register block to read from (See [W5500], section
|
|
* 2.2.2 Control Phase)
|
|
* offset - Offset address of the register to read.
|
|
* value - The 16-bit value read in host byte-order.
|
|
*
|
|
* Returned Value:
|
|
* OK in case of success. A negated errno value in case of failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
|
|
uint8_t block_select_bits,
|
|
uint16_t offset,
|
|
FAR uint16_t *value)
|
|
{
|
|
int i;
|
|
|
|
*value = w5500_read16(self, block_select_bits, offset);
|
|
|
|
for (i = 0; i < 100; i++)
|
|
{
|
|
uint16_t temp;
|
|
|
|
temp = w5500_read16(self, block_select_bits, offset);
|
|
|
|
if (*value == temp)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
*value = temp;
|
|
}
|
|
|
|
nerr("Failed to get consistent value.\n");
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
/* Ethernet frame transmission buffer management ****************************/
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_txbuf_reset
|
|
*
|
|
* Description:
|
|
* Reset state that manages the storage of multiple outgoing Ethernet
|
|
* frames in W5500's 16KiB Tx RAM.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
|
|
{
|
|
memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
|
|
self->txbuf_rdptr = 0;
|
|
self->txbuf_wrptr = 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_txbuf_numfree
|
|
*
|
|
* Description:
|
|
* Return the number of Ethernet frames that can still be stored in W5500's
|
|
* 16KiB Tx RAM.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
*
|
|
* Returned Value:
|
|
* The number of Ethernet frames that can still be stored.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
|
|
{
|
|
if (self->txbuf_wrptr >= self->txbuf_rdptr)
|
|
{
|
|
return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
|
|
}
|
|
else
|
|
{
|
|
return self->txbuf_rdptr - self->txbuf_wrptr - 1;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_txbuf_numpending
|
|
*
|
|
* Description:
|
|
* Return the number of Ethernet frames that are still pending for trans-
|
|
* mission from W5500's 16KiB Tx RAM.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
*
|
|
* Returned Value:
|
|
* The number of Ethernet frames that are pending for transmission.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
|
|
{
|
|
return NUM_TXBUFS - w5500_txbuf_numfree(self);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_txbuf_copy
|
|
*
|
|
* Description:
|
|
* Copy an Ethernet frame from the socket device's buffer to the W5500's
|
|
* 16KiB Tx RAM.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
*
|
|
* Returned Value:
|
|
* The byte offset of the first byte after the frame that was copied into
|
|
* W5500's 16KiB Tx RAM.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
|
|
{
|
|
uint16_t offset;
|
|
|
|
DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
|
|
|
|
offset = self->txbuf_offset[self->txbuf_wrptr];
|
|
|
|
w5500_write(self,
|
|
W5500_BSB_SOCKET_TX_BUFFER(0),
|
|
offset,
|
|
self->sk_dev.d_buf,
|
|
self->sk_dev.d_len);
|
|
|
|
self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
|
|
self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
|
|
|
|
return self->txbuf_offset[self->txbuf_wrptr];
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_txbuf_next
|
|
*
|
|
* Description:
|
|
* Release storage for an Ethernet frame that has been successfully
|
|
* transmitted. Also triggers transmission of the next Ethernet frame,
|
|
* if applicable.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
*
|
|
* Returned Value:
|
|
* Whether transmission of another Ethernet frame was triggered.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
|
|
{
|
|
uint16_t offset;
|
|
|
|
DEBUGASSERT(w5500_txbuf_numpending(self));
|
|
|
|
self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
|
|
|
|
offset = w5500_read16(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_TX_RD0);
|
|
|
|
DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
|
|
|
|
if (!w5500_txbuf_numpending(self))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
|
|
|
|
w5500_write16(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_TX_WR0,
|
|
offset);
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_CR,
|
|
SN_CR_SEND);
|
|
|
|
/* (Re-)start the TX timeout watchdog timer */
|
|
|
|
wd_start(&self->sk_txtimeout,
|
|
W5500_TXTIMEOUT,
|
|
w5500_txtimeout_expiry,
|
|
(wdparm_t)self);
|
|
|
|
return true;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_fence
|
|
*
|
|
* Description:
|
|
* Put the W5500 into reset and disable respective interrupt handling.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_fence(FAR struct w5500_driver_s *self)
|
|
{
|
|
self->lower->enable(self->lower, false);
|
|
w5500_reset(self, true); /* Reset and keep reset asserted */
|
|
self->sk_bifup = false;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_unfence
|
|
*
|
|
* Description:
|
|
* Release W5500 from reset, initialize it and wait up to ten seconds
|
|
* for link up.
|
|
*
|
|
* Input Parameters:
|
|
* self - The respective w5500 device
|
|
*
|
|
* Returned Value:
|
|
* OK in case of success. A negated errno value in case of failure, in
|
|
* which case the W5500 is fenced again.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w5500_unfence(FAR struct w5500_driver_s *self)
|
|
{
|
|
uint8_t value;
|
|
int i;
|
|
|
|
/* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
|
|
|
|
w5500_reset(self, false); /* Reset sequence and keep reset de-asserted */
|
|
|
|
/* Set the Ethernet interface's MAC address */
|
|
|
|
w5500_write(self,
|
|
W5500_BSB_COMMON_REGS,
|
|
W5500_SHAR0, /* Source Hardware Address Register */
|
|
self->sk_dev.d_mac.ether.ether_addr_octet,
|
|
sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
|
|
|
|
/* Configure socket 0 for raw MAC access with MAC filtering enabled. */
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_MR,
|
|
SM_MR_MACRAW | SN_MR_MFEN);
|
|
|
|
/* Allocate all TX and RX buffer space to socket 0 ... */
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_RXBUF_SIZE,
|
|
SN_RXBUF_16KB);
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_TXBUF_SIZE,
|
|
SN_TXBUF_16KB);
|
|
|
|
/* ... and none to sockets 1 to 7. */
|
|
|
|
for (i = 1; i < 8; i++)
|
|
{
|
|
w5500_write8(self,
|
|
W5500_BSB_SOCKET_REGS(i),
|
|
W5500_SN_RXBUF_SIZE,
|
|
SN_RXBUF_0KB);
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_SOCKET_REGS(i),
|
|
W5500_SN_TXBUF_SIZE,
|
|
SN_TXBUF_0KB);
|
|
}
|
|
|
|
/* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
|
|
* enabled as long as a transmission is in progress).
|
|
*/
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_IMR,
|
|
SN_INT_RECV);
|
|
|
|
/* Enable interrupts on socket 0 */
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_COMMON_REGS,
|
|
W5500_SIMR, /* Socket Interrupt Mask Register */
|
|
SIMR(0));
|
|
|
|
/* Open socket 0 */
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_CR, /* Control Register */
|
|
SN_CR_OPEN);
|
|
|
|
/* Check whether socket 0 is open in MACRAW mode. */
|
|
|
|
value = w5500_read8(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_SR);
|
|
|
|
if (value != SN_SR_SOCK_MACRAW)
|
|
{
|
|
nerr("Unexpected status: %02" PRIx8 "\n", value);
|
|
goto error;
|
|
}
|
|
|
|
/* Reset Tx buffer management state. */
|
|
|
|
w5500_txbuf_reset(self);
|
|
|
|
/* Wait up to 10 seconds for link-up */
|
|
|
|
value = w5500_read8(self,
|
|
W5500_BSB_COMMON_REGS,
|
|
W5500_PHYCFGR);
|
|
|
|
for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
|
|
{
|
|
value = w5500_read8(self,
|
|
W5500_BSB_COMMON_REGS,
|
|
W5500_PHYCFGR);
|
|
|
|
nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
|
|
}
|
|
|
|
if (value & PHYCFGR_LNK)
|
|
{
|
|
ninfo("Link up (%d Mbps / %s duplex)\n",
|
|
(value & PHYCFGR_SPD) ? 100 : 10,
|
|
(value & PHYCFGR_DPX) ? "full" : "half");
|
|
}
|
|
else
|
|
{
|
|
nwarn("Link still down. Cable plugged?\n");
|
|
goto error;
|
|
}
|
|
|
|
return OK;
|
|
|
|
error:
|
|
w5500_fence(self);
|
|
return -EIO;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_transmit
|
|
*
|
|
* Description:
|
|
* Start hardware transmission. Called either from the txdone interrupt
|
|
* handling or from watchdog based polling.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; a negated errno on failure
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_transmit(FAR struct w5500_driver_s *self)
|
|
{
|
|
uint16_t offset;
|
|
|
|
/* Verify that the hardware is ready to send another packet. If we get
|
|
* here, then we are committed to sending a packet; Higher level logic
|
|
* must have assured that there is no transmission in progress.
|
|
*/
|
|
|
|
if (!w5500_txbuf_numfree(self))
|
|
{
|
|
ninfo("Dropping Tx packet due to no buffer available.\n");
|
|
NETDEV_TXERRORS(self->sk_dev);
|
|
return;
|
|
}
|
|
|
|
/* Increment statistics */
|
|
|
|
NETDEV_TXPACKETS(self->sk_dev);
|
|
|
|
/* Copy packet data to TX buffer */
|
|
|
|
offset = w5500_txbuf_copy(self);
|
|
|
|
/* If there have not been any Tx buffers in use this means we need to start
|
|
* transmission. Otherwise, this is done either in w5500_txdone or in
|
|
* w5500_txtimeout_work.
|
|
*/
|
|
|
|
if (w5500_txbuf_numpending(self) == 1)
|
|
{
|
|
/* Set TX Write Pointer to indicate packet length */
|
|
|
|
w5500_write16(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_TX_WR0,
|
|
offset);
|
|
|
|
/* Enable Tx interrupts (Rx ones are always enabled). */
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_IMR,
|
|
SN_INT_RECV | SN_INT_SEND_OK);
|
|
|
|
/* Send the packet */
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_CR, /* Control Register */
|
|
SN_CR_SEND);
|
|
|
|
/* Setup the TX timeout watchdog (perhaps restarting the timer) */
|
|
|
|
wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
|
|
w5500_txtimeout_expiry, (wdparm_t)self);
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_NET_INFO
|
|
ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_txpoll
|
|
*
|
|
* Description:
|
|
* The transmitter is available, check if the network has any outgoing
|
|
* packets ready to send. This is a callback from devif_poll().
|
|
* devif_poll() may be called:
|
|
*
|
|
* 1. When the preceding TX packet send is complete,
|
|
* 2. When the preceding TX packet send timesout and the interface is reset
|
|
* 3. During normal TX polling
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; a negated errno on failure
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w5500_txpoll(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct w5500_driver_s *self =
|
|
(FAR struct w5500_driver_s *)dev->d_private;
|
|
|
|
/* If the polling resulted in data that should be sent out on the network,
|
|
* the field d_len is set to a value > 0.
|
|
*/
|
|
|
|
if (self->sk_dev.d_len > 0)
|
|
{
|
|
/* Look up the destination MAC address and add it to the Ethernet
|
|
* header.
|
|
*/
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
if (IFF_IS_IPv4(self->sk_dev.d_flags))
|
|
{
|
|
arp_out(&self->sk_dev);
|
|
}
|
|
#endif /* CONFIG_NET_IPv4 */
|
|
|
|
#ifdef CONFIG_NET_IPv6
|
|
if (IFF_IS_IPv6(self->sk_dev.d_flags))
|
|
{
|
|
neighbor_out(&self->sk_dev);
|
|
}
|
|
#endif /* CONFIG_NET_IPv6 */
|
|
|
|
/* Check if the network is sending this packet to the IP address of
|
|
* this device. If so, just loop the packet back into the network but
|
|
* don't attempt to put it on the wire.
|
|
*/
|
|
|
|
if (!devif_loopback(&self->sk_dev))
|
|
{
|
|
/* Send the packet */
|
|
|
|
w5500_transmit(self);
|
|
|
|
/* Check if there is room in the device to hold another packet.
|
|
* If not, return a non-zero value to terminate the poll.
|
|
*/
|
|
|
|
if (!w5500_txbuf_numfree(self))
|
|
{
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If zero is returned, the polling will continue until all connections
|
|
* have been examined.
|
|
*/
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_reply
|
|
*
|
|
* Description:
|
|
* After a packet has been received and dispatched to the network, it
|
|
* may return with an outgoing packet. This function checks for that case
|
|
* and performs the transmission if necessary.
|
|
*
|
|
* Input Parameters:
|
|
* self - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_reply(struct w5500_driver_s *self)
|
|
{
|
|
/* If the packet dispatch resulted in data that should be sent out on the
|
|
* network, the field d_len will set to a value > 0.
|
|
*/
|
|
|
|
if (self->sk_dev.d_len > 0)
|
|
{
|
|
/* Update the Ethernet header with the correct MAC address */
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
if (IFF_IS_IPv4(self->sk_dev.d_flags))
|
|
{
|
|
arp_out(&self->sk_dev);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_NET_IPv6
|
|
if (IFF_IS_IPv6(self->sk_dev.d_flags))
|
|
{
|
|
neighbor_out(&self->sk_dev);
|
|
}
|
|
#endif
|
|
|
|
/* And send the packet */
|
|
|
|
w5500_transmit(self);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_receive
|
|
*
|
|
* Description:
|
|
* An interrupt was received indicating the availability of a new RX packet
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_receive(FAR struct w5500_driver_s *self)
|
|
{
|
|
do
|
|
{
|
|
uint16_t s0_rx_rd;
|
|
uint16_t s0_rx_rsr;
|
|
uint16_t pktlen;
|
|
int ret;
|
|
|
|
/* Check if the packet is a valid size for the network buffer
|
|
* configuration.
|
|
*/
|
|
|
|
ret = w5500_read16_atomic(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_RX_RSR0,
|
|
&s0_rx_rsr);
|
|
if (ret != OK)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
if (s0_rx_rsr == 0)
|
|
{
|
|
ninfo("No data left to read. We are done.\n");
|
|
break;
|
|
}
|
|
|
|
/* The W5500 prepends each packet with a 2-byte length field in
|
|
* network byte order. The length value includes the length field
|
|
* itself. At least this 2-byte packet length must be available.
|
|
*/
|
|
|
|
if (s0_rx_rsr < sizeof(pktlen))
|
|
{
|
|
nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
|
|
goto error;
|
|
}
|
|
|
|
/* Get the Socket 0 RX Read Pointer. */
|
|
|
|
s0_rx_rd = w5500_read16(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_RX_RD0);
|
|
|
|
/* Read 16-bit length field. */
|
|
|
|
pktlen = w5500_read16(self,
|
|
W5500_BSB_SOCKET_RX_BUFFER(0),
|
|
s0_rx_rd);
|
|
|
|
if (pktlen > s0_rx_rsr)
|
|
{
|
|
nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
|
|
pktlen,
|
|
s0_rx_rd);
|
|
|
|
goto error;
|
|
}
|
|
|
|
if (pktlen < s0_rx_rsr)
|
|
{
|
|
ninfo("More than one packet in RX buffer. "
|
|
"pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
|
|
pktlen,
|
|
s0_rx_rsr);
|
|
}
|
|
|
|
self->sk_dev.d_len = pktlen - sizeof(pktlen);
|
|
|
|
/* Copy the data data from the hardware to priv->sk_dev.d_buf. Set
|
|
* amount of data in priv->sk_dev.d_len
|
|
*/
|
|
|
|
if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
|
|
{
|
|
w5500_read(self,
|
|
W5500_BSB_SOCKET_RX_BUFFER(0),
|
|
s0_rx_rd + sizeof(pktlen),
|
|
self->sk_dev.d_buf,
|
|
self->sk_dev.d_len);
|
|
}
|
|
|
|
/* Acknowledge data reception to W5500 */
|
|
|
|
w5500_write16(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_RX_RD0,
|
|
s0_rx_rd + pktlen);
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_CR,
|
|
SN_CR_RECV);
|
|
|
|
/* Check for errors and update statistics */
|
|
|
|
if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
|
|
self->sk_dev.d_len < ETH_HDRLEN)
|
|
{
|
|
nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
|
|
self->sk_dev.d_len = 0;
|
|
NETDEV_RXERRORS(&priv->dev);
|
|
continue;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_NET_INFO
|
|
ninfodumpbuffer("Received Packet:",
|
|
self->sk_dev.d_buf,
|
|
self->sk_dev.d_len);
|
|
#endif
|
|
|
|
#ifdef CONFIG_NET_PKT
|
|
/* When packet sockets are enabled, feed the frame into the tap */
|
|
|
|
pkt_input(&self->sk_dev);
|
|
#endif
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
/* Check for an IPv4 packet */
|
|
|
|
if (ETH_HDR->type == HTONS(ETHTYPE_IP))
|
|
{
|
|
ninfo("IPv4 frame\n");
|
|
NETDEV_RXIPV4(&self->sk_dev);
|
|
|
|
/* Handle ARP on input, then dispatch IPv4 packet to the network
|
|
* layer.
|
|
*/
|
|
|
|
arp_ipin(&self->sk_dev);
|
|
ipv4_input(&self->sk_dev);
|
|
|
|
/* Check for a reply to the IPv4 packet */
|
|
|
|
w5500_reply(self);
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef CONFIG_NET_IPv6
|
|
/* Check for an IPv6 packet */
|
|
|
|
if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
|
|
{
|
|
ninfo("IPv6 frame\n");
|
|
NETDEV_RXIPV6(&self->sk_dev);
|
|
|
|
/* Dispatch IPv6 packet to the network layer */
|
|
|
|
ipv6_input(&self->sk_dev);
|
|
|
|
/* Check for a reply to the IPv6 packet */
|
|
|
|
w5500_reply(self);
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef CONFIG_NET_ARP
|
|
/* Check for an ARP packet */
|
|
|
|
if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
|
|
{
|
|
ninfo("ARP frame\n");
|
|
|
|
/* Dispatch ARP packet to the network layer */
|
|
|
|
arp_arpin(&self->sk_dev);
|
|
NETDEV_RXARP(&self->sk_dev);
|
|
|
|
/* If the above function invocation resulted in data that should be
|
|
* sent out on the network, the field d_len will set to a value
|
|
* > 0.
|
|
*/
|
|
|
|
if (self->sk_dev.d_len > 0)
|
|
{
|
|
w5500_transmit(self);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
ninfo("Dropped frame\n");
|
|
|
|
NETDEV_RXDROPPED(&self->sk_dev);
|
|
}
|
|
}
|
|
while (true); /* While there are more packets to be processed */
|
|
|
|
return;
|
|
|
|
error:
|
|
w5500_fence(self);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_txdone
|
|
*
|
|
* Description:
|
|
* An interrupt was received indicating that the last TX packet(s) is done
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_txdone(FAR struct w5500_driver_s *self)
|
|
{
|
|
/* Check for errors and update statistics */
|
|
|
|
NETDEV_TXDONE(self->sk_dev);
|
|
|
|
/* Check if there are pending transmissions. */
|
|
|
|
if (!w5500_txbuf_next(self))
|
|
{
|
|
ninfo("No further transmissions pending.\n");
|
|
|
|
/* If no further transmissions are pending, then cancel the TX timeout
|
|
* and disable further Tx interrupts.
|
|
*/
|
|
|
|
wd_cancel(&self->sk_txtimeout);
|
|
|
|
/* And disable further TX interrupts. */
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_IMR, SN_INT_RECV);
|
|
}
|
|
|
|
/* In any event, poll the network for new TX data */
|
|
|
|
devif_poll(&self->sk_dev, w5500_txpoll);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_interrupt_work
|
|
*
|
|
* Description:
|
|
* Perform interrupt related work from the worker thread
|
|
*
|
|
* Input Parameters:
|
|
* arg - The argument passed when work_queue() was called.
|
|
*
|
|
* Returned Value:
|
|
* OK on success
|
|
*
|
|
* Assumptions:
|
|
* Runs on a worker thread.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_interrupt_work(FAR void *arg)
|
|
{
|
|
FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
|
|
uint8_t ir[3];
|
|
|
|
/* Lock the network and serialize driver operations if necessary.
|
|
* NOTE: Serialization is only required in the case where the driver work
|
|
* is performed on an LP worker thread and where more than one LP worker
|
|
* thread has been configured.
|
|
*/
|
|
|
|
net_lock();
|
|
|
|
/* Process pending Ethernet interrupts. Read IR, MIR and SIR in one shot
|
|
* to optimize latency, although MIR is not actually used.
|
|
*/
|
|
|
|
w5500_read(self,
|
|
W5500_BSB_COMMON_REGS,
|
|
W5500_IR,
|
|
ir,
|
|
sizeof(ir));
|
|
|
|
/* We expect none of the common (integrated network stack related)
|
|
* interrupts and only interrupts from socket 0.
|
|
*/
|
|
|
|
if (ir[0] != 0)
|
|
{
|
|
nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_COMMON_REGS,
|
|
W5500_IR,
|
|
ir[0]);
|
|
}
|
|
|
|
if (ir[2] & ~SIR(0))
|
|
{
|
|
nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
|
|
ir[2]);
|
|
|
|
goto error;
|
|
}
|
|
|
|
if (ir[2] == 0)
|
|
{
|
|
nwarn("Overinitiative interrupt work.\n");
|
|
|
|
goto done;
|
|
}
|
|
|
|
/* Get and clear interrupt status bits */
|
|
|
|
ir[0] = w5500_read8(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_IR);
|
|
|
|
if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
|
|
{
|
|
nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
|
|
|
|
goto error;
|
|
}
|
|
|
|
w5500_write8(self,
|
|
W5500_BSB_SOCKET_REGS(0),
|
|
W5500_SN_IR,
|
|
ir[0]);
|
|
|
|
/* Handle interrupts according to status bit settings */
|
|
|
|
/* Check if a packet transmission just completed. If so, call
|
|
* w5500_txdone. This may disable further Tx interrupts if there are no
|
|
* pending transmissions.
|
|
*/
|
|
|
|
if (ir[0] & SN_INT_SEND_OK)
|
|
{
|
|
w5500_txdone(self);
|
|
}
|
|
|
|
/* Check if we received an incoming packet, if so, call w5500_receive() */
|
|
|
|
if (ir[0] & SN_INT_RECV)
|
|
{
|
|
w5500_receive(self);
|
|
}
|
|
|
|
done:
|
|
net_unlock();
|
|
|
|
/* Re-enable Ethernet interrupts */
|
|
|
|
self->lower->enable(self->lower, true);
|
|
|
|
return;
|
|
|
|
error:
|
|
w5500_fence(self);
|
|
net_unlock();
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_interrupt
|
|
*
|
|
* Description:
|
|
* Hardware interrupt handler
|
|
*
|
|
* Input Parameters:
|
|
* irq - Number of the IRQ that generated the interrupt
|
|
* context - Interrupt register state save info (architecture-specific)
|
|
*
|
|
* Returned Value:
|
|
* OK on success
|
|
*
|
|
* Assumptions:
|
|
* Runs in the context of a the Ethernet interrupt handler. Local
|
|
* interrupts are disabled by the interrupt logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
|
|
{
|
|
FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
|
|
|
|
DEBUGASSERT(self != NULL);
|
|
|
|
/* Disable further Ethernet interrupts. Because Ethernet interrupts are
|
|
* also disabled if the TX timeout event occurs, there can be no race
|
|
* condition here.
|
|
*/
|
|
|
|
self->lower->enable(self->lower, false);
|
|
|
|
/* Schedule to perform the interrupt processing on the worker thread. */
|
|
|
|
work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_txtimeout_work
|
|
*
|
|
* Description:
|
|
* Perform TX timeout related work from the worker thread
|
|
*
|
|
* Input Parameters:
|
|
* arg - The argument passed when work_queue() as called.
|
|
*
|
|
* Returned Value:
|
|
* OK on success
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_txtimeout_work(FAR void *arg)
|
|
{
|
|
FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
|
|
|
|
/* Lock the network and serialize driver operations if necessary.
|
|
* NOTE: Serialization is only required in the case where the driver work
|
|
* is performed on an LP worker thread and where more than one LP worker
|
|
* thread has been configured.
|
|
*/
|
|
|
|
net_lock();
|
|
|
|
/* Increment statistics and dump debug info */
|
|
|
|
NETDEV_TXTIMEOUTS(self->sk_dev);
|
|
|
|
/* Then reset the hardware */
|
|
|
|
if (w5500_unfence(self) == OK)
|
|
{
|
|
self->lower->enable(self->lower, true);
|
|
|
|
/* Then poll the network for new XMIT data */
|
|
|
|
devif_poll(&self->sk_dev, w5500_txpoll);
|
|
}
|
|
|
|
net_unlock();
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_txtimeout_expiry
|
|
*
|
|
* Description:
|
|
* Our TX watchdog timed out. Called from the timer interrupt handler.
|
|
* The last TX never completed. Reset the hardware and start again.
|
|
*
|
|
* Input Parameters:
|
|
* arg - The argument
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Runs in the context of a the timer interrupt handler. Local
|
|
* interrupts are disabled by the interrupt logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_txtimeout_expiry(wdparm_t arg)
|
|
{
|
|
FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
|
|
|
|
/* Disable further Ethernet interrupts. This will prevent some race
|
|
* conditions with interrupt work. There is still a potential race
|
|
* condition with interrupt work that is already queued and in progress.
|
|
*/
|
|
|
|
w5500_fence(self);
|
|
|
|
/* Schedule to perform the TX timeout processing on the worker thread. */
|
|
|
|
work_queue(ETHWORK, &self->sk_irqwork, w5500_txtimeout_work, self, 0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_ifup
|
|
*
|
|
* Description:
|
|
* NuttX Callback: Bring up the Ethernet interface when an IP address is
|
|
* provided
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w5500_ifup(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct w5500_driver_s *self =
|
|
(FAR struct w5500_driver_s *)dev->d_private;
|
|
int ret;
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
ninfo("Bringing up: %d.%d.%d.%d\n",
|
|
(int)dev->d_ipaddr & 0xff,
|
|
(int)(dev->d_ipaddr >> 8) & 0xff,
|
|
(int)(dev->d_ipaddr >> 16) & 0xff,
|
|
(int)dev->d_ipaddr >> 24);
|
|
#endif
|
|
#ifdef CONFIG_NET_IPv6
|
|
ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
|
|
dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2],
|
|
dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5],
|
|
dev->d_ipv6addr[6], dev->d_ipv6addr[7]);
|
|
#endif
|
|
|
|
/* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
|
|
|
|
ret = w5500_unfence(self);
|
|
|
|
if (ret != OK)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_NET_ICMPv6
|
|
/* Set up IPv6 multicast address filtering */
|
|
|
|
w5500_ipv6multicast(self);
|
|
#endif
|
|
|
|
/* Enable the Ethernet interrupt */
|
|
|
|
self->sk_bifup = true;
|
|
self->lower->enable(self->lower, true);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_ifdown
|
|
*
|
|
* Description:
|
|
* NuttX Callback: Stop the interface.
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w5500_ifdown(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct w5500_driver_s *self =
|
|
(FAR struct w5500_driver_s *)dev->d_private;
|
|
irqstate_t flags;
|
|
|
|
/* Disable the Ethernet interrupt */
|
|
|
|
flags = enter_critical_section();
|
|
self->lower->enable(self->lower, false);
|
|
|
|
/* Cancel the TX timeout timer */
|
|
|
|
wd_cancel(&self->sk_txtimeout);
|
|
|
|
/* Put the EMAC in its reset, non-operational state. This should be
|
|
* a known configuration that will guarantee the w5500_ifup() always
|
|
* successfully brings the interface back up.
|
|
*/
|
|
|
|
w5500_fence(self);
|
|
|
|
/* Mark the device "down" */
|
|
|
|
self->sk_bifup = false;
|
|
leave_critical_section(flags);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_txavail_work
|
|
*
|
|
* Description:
|
|
* Perform an out-of-cycle poll on the worker thread.
|
|
*
|
|
* Input Parameters:
|
|
* arg - Reference to the NuttX driver state structure (cast to void*)
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Runs on a work queue thread.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w5500_txavail_work(FAR void *arg)
|
|
{
|
|
FAR struct w5500_driver_s *priv = (FAR struct w5500_driver_s *)arg;
|
|
|
|
/* Lock the network and serialize driver operations if necessary.
|
|
* NOTE: Serialization is only required in the case where the driver work
|
|
* is performed on an LP worker thread and where more than one LP worker
|
|
* thread has been configured.
|
|
*/
|
|
|
|
net_lock();
|
|
|
|
/* Ignore the notification if the interface is not yet up */
|
|
|
|
if (priv->sk_bifup)
|
|
{
|
|
/* Check if there is room in the hardware to hold another packet. */
|
|
|
|
/* If so, then poll the network for new XMIT data */
|
|
|
|
devif_poll(&priv->sk_dev, w5500_txpoll);
|
|
}
|
|
|
|
net_unlock();
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_txavail
|
|
*
|
|
* Description:
|
|
* Driver callback invoked when new TX data is available. This is a
|
|
* stimulus perform an out-of-cycle poll and, thereby, reduce the TX
|
|
* latency.
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w5500_txavail(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct w5500_driver_s *priv =
|
|
(FAR struct w5500_driver_s *)dev->d_private;
|
|
|
|
/* Is our single work structure available? It may not be if there are
|
|
* pending interrupt actions and we will have to ignore the Tx
|
|
* availability action.
|
|
*/
|
|
|
|
if (work_available(&priv->sk_pollwork))
|
|
{
|
|
/* Schedule to serialize the poll on the worker thread. */
|
|
|
|
work_queue(ETHWORK, &priv->sk_pollwork, w5500_txavail_work, priv, 0);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_addmac
|
|
*
|
|
* Description:
|
|
* NuttX Callback: Add the specified MAC address to the hardware multicast
|
|
* address filtering
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
* mac - The MAC address to be added
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
|
|
static int w5500_addmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
|
|
{
|
|
FAR struct w5500_driver_s *priv =
|
|
(FAR struct w5500_driver_s *)dev->d_private;
|
|
|
|
/* Add the MAC address to the hardware multicast routing table */
|
|
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_rmmac
|
|
*
|
|
* Description:
|
|
* NuttX Callback: Remove the specified MAC address from the hardware
|
|
* multicast address filtering
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
* mac - The MAC address to be removed
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NET_MCASTGROUP
|
|
static int w5500_rmmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
|
|
{
|
|
FAR struct w5500_driver_s *priv =
|
|
(FAR struct w5500_driver_s *)dev->d_private;
|
|
|
|
/* Add the MAC address to the hardware multicast routing table */
|
|
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_ipv6multicast
|
|
*
|
|
* Description:
|
|
* Configure the IPv6 multicast MAC address.
|
|
*
|
|
* Input Parameters:
|
|
* priv - A reference to the private driver state structure
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NET_ICMPv6
|
|
static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv)
|
|
{
|
|
FAR struct net_driver_s *dev;
|
|
uint16_t tmp16;
|
|
uint8_t mac[6];
|
|
|
|
/* For ICMPv6, we need to add the IPv6 multicast address
|
|
*
|
|
* For IPv6 multicast addresses, the Ethernet MAC is derived by
|
|
* the four low-order octets OR'ed with the MAC 33:33:00:00:00:00,
|
|
* so for example the IPv6 address FF02:DEAD:BEEF::1:3 would map
|
|
* to the Ethernet MAC address 33:33:00:01:00:03.
|
|
*
|
|
* NOTES: This appears correct for the ICMPv6 Router Solicitation
|
|
* Message, but the ICMPv6 Neighbor Solicitation message seems to
|
|
* use 33:33:ff:01:00:03.
|
|
*/
|
|
|
|
mac[0] = 0x33;
|
|
mac[1] = 0x33;
|
|
|
|
dev = &priv->dev;
|
|
tmp16 = dev->d_ipv6addr[6];
|
|
mac[2] = 0xff;
|
|
mac[3] = tmp16 >> 8;
|
|
|
|
tmp16 = dev->d_ipv6addr[7];
|
|
mac[4] = tmp16 & 0xff;
|
|
mac[5] = tmp16 >> 8;
|
|
|
|
ninfo("IPv6 Multicast: %02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
|
|
w5500_addmac(dev, mac);
|
|
|
|
#ifdef CONFIG_NET_ICMPv6_AUTOCONF
|
|
/* Add the IPv6 all link-local nodes Ethernet address. This is the
|
|
* address that we expect to receive ICMPv6 Router Advertisement
|
|
* packets.
|
|
*/
|
|
|
|
w5500_addmac(dev, g_ipv6_ethallnodes.ether_addr_octet);
|
|
|
|
#endif /* CONFIG_NET_ICMPv6_AUTOCONF */
|
|
|
|
#ifdef CONFIG_NET_ICMPv6_ROUTER
|
|
/* Add the IPv6 all link-local routers Ethernet address. This is the
|
|
* address that we expect to receive ICMPv6 Router Solicitation
|
|
* packets.
|
|
*/
|
|
|
|
w5500_addmac(dev, g_ipv6_ethallrouters.ether_addr_octet);
|
|
|
|
#endif /* CONFIG_NET_ICMPv6_ROUTER */
|
|
}
|
|
#endif /* CONFIG_NET_ICMPv6 */
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_ioctl
|
|
*
|
|
* Description:
|
|
* Handle network IOCTL commands directed to this device.
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
* cmd - The IOCTL command
|
|
* arg - The argument for the IOCTL command
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NETDEV_IOCTL
|
|
static int w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
|
|
unsigned long arg)
|
|
{
|
|
FAR struct w5500_driver_s *priv =
|
|
(FAR struct w5500_driver_s *)dev->d_private;
|
|
int ret;
|
|
|
|
/* Decode and dispatch the driver-specific IOCTL command */
|
|
|
|
switch (cmd)
|
|
{
|
|
/* Add cases here to support the IOCTL commands */
|
|
|
|
default:
|
|
nerr("ERROR: Unrecognized IOCTL command: %d\n", command);
|
|
return -ENOTTY; /* Special return value for this case */
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: w5500_initialize
|
|
*
|
|
* Description:
|
|
* Initialize the Ethernet controller and driver
|
|
*
|
|
* Parameters:
|
|
* spi - A reference to the platform's SPI driver for the W5500.
|
|
* lower - The lower half driver instance for this W5500 chip.
|
|
* devno - If more than one W5500 is supported, then this is the
|
|
* zero based number that identifies the W5500.
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
int w5500_initialize(FAR struct spi_dev_s *spi_dev,
|
|
FAR const struct w5500_lower_s *lower,
|
|
unsigned int devno)
|
|
{
|
|
FAR struct w5500_driver_s *self;
|
|
|
|
/* Get the interface structure associated with this interface number. */
|
|
|
|
DEBUGASSERT(devno < CONFIG_NET_W5500_NINTERFACES);
|
|
self = &g_w5500[devno];
|
|
|
|
/* Check if a Ethernet chip is recognized at its I/O base */
|
|
|
|
/* Attach the IRQ to the driver */
|
|
|
|
if (lower->attach(lower, w5500_interrupt, self))
|
|
{
|
|
/* We could not attach the ISR to the interrupt */
|
|
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/* Initialize the driver structure */
|
|
|
|
memset(self, 0, sizeof(struct w5500_driver_s));
|
|
self->sk_dev.d_buf = g_pktbuf; /* Single packet buffer */
|
|
self->sk_dev.d_ifup = w5500_ifup; /* I/F up (new IP address) callback */
|
|
self->sk_dev.d_ifdown = w5500_ifdown; /* I/F down callback */
|
|
self->sk_dev.d_txavail = w5500_txavail; /* New TX data callback */
|
|
#ifdef CONFIG_NET_MCASTGROUP
|
|
self->sk_dev.d_addmac = w5500_addmac; /* Add multicast MAC address */
|
|
self->sk_dev.d_rmmac = w5500_rmmac; /* Remove multicast MAC address */
|
|
#endif
|
|
#ifdef CONFIG_NETDEV_IOCTL
|
|
self->sk_dev.d_ioctl = w5500_ioctl; /* Handle network IOCTL commands */
|
|
#endif
|
|
self->sk_dev.d_private = g_w5500; /* Used to recover private state from dev */
|
|
self->spi_dev = spi_dev; /* SPI hardware interconnect */
|
|
self->lower = lower; /* Low-level MCU specific support */
|
|
|
|
/* Put the interface in the down state. This usually amounts to resetting
|
|
* the device and/or calling w5500_ifdown().
|
|
*/
|
|
|
|
w5500_ifdown(&self->sk_dev);
|
|
|
|
/* Register the device with the OS so that socket IOCTLs can be performed */
|
|
|
|
netdev_register(&self->sk_dev, NET_LL_ETHERNET);
|
|
return OK;
|
|
}
|
|
|
|
#endif /* !defined(CONFIG_SCHED_WORKQUEUE) */
|
|
|
|
#endif /* CONFIG_NET_W5500 */
|