summaryrefslogtreecommitdiffstats
path: root/bsps/powerpc/haleakala/net/network.c
diff options
context:
space:
mode:
Diffstat (limited to 'bsps/powerpc/haleakala/net/network.c')
-rw-r--r--bsps/powerpc/haleakala/net/network.c1249
1 files changed, 0 insertions, 1249 deletions
diff --git a/bsps/powerpc/haleakala/net/network.c b/bsps/powerpc/haleakala/net/network.c
deleted file mode 100644
index 41481255cb..0000000000
--- a/bsps/powerpc/haleakala/net/network.c
+++ /dev/null
@@ -1,1249 +0,0 @@
-/*
- * network.c
- * RTEMS_490
- *
- * Created by Michael Hamel on 18/11/08.
- *
- * This code is for the PPC405EX, 405EXr on the Haleakala board with an
- * 88E1111 PHY.
- * Its has some adaptations for the 405GP, and 405GPr (untested).
- *
- * It should handle dual interfaces correctly, but has not been tested.
- *
- */
-
-#include <machine/rtems-bsd-kernel-space.h>
-
-#include <bsp.h>
-#include <stdio.h>
-#include <errno.h>
-#include <rtems/error.h>
-#include <rtems/rtems_bsdnet.h>
-#include <rtems/rtems_mii_ioctl.h>
-#include <rtems/score/assert.h>
-
-#include <sys/param.h>
-#include <sys/mbuf.h>
-#include <sys/socket.h>
-#include <sys/sockio.h>
-
-#include <net/if.h>
-
-#include <netinet/in.h>
-
-#include <netinet/if_ether.h>
-#include <bsp/irq.h>
-
-#include <sys/types.h>
-#include <sys/socket.h>
-
-#include <ppc4xx/ppc405gp.h>
-#include <ppc4xx/ppc405ex.h>
-
-#define qDebug /* General printf debugging */
-/* #define qMultiDebug */ /* Debugging for the multicast hardware filter */
-
-/*---------------------------- Hardware definitions -------------------------- */
-
-/* PHY addresses for Kilauea & Haleakala; defined by hardware */
-enum {
- kPHY0 = 1,
- kPHY1 = 2,
- kMaxEMACs = 2
-};
-
-enum {
- kMaxRxBuffers = 256,
- kNXmtDescriptors = 256, /* May as well use all of them */
- kNoXmtBusy = 666 /* Arbitrary flag value outside 0..kNXmtDescriptors */
-};
-
-
-/*----------------------- Local variables for the driver -------------------------- */
-
-typedef struct MALDescriptor {
- uint16_t ctrlBits;
- uint16_t adrDataSize; /* 4 bits of high address, 12 bits of length */
- uint8_t* ptr;
-} MALDescriptor;
-
-typedef struct EMACLocals {
- struct arpcom arpcom;
-
- /* Pointer to memory-mapped hardware */
- volatile EthernetRegisters_GP* EMAC;
-
- /* Transmit and receive task references */
- rtems_id rxDaemonTid;
- rtems_id txDaemonTid;
- int nRxBuffers;
- int xmtFreeIndex;
- int xmtBusyIndex;
- MALDescriptor* xmtDescTable;
- MALDescriptor* rcvDescTable;
-
- struct mbuf* rxMBufs[kMaxRxBuffers];
- struct mbuf* txMBufs[kNXmtDescriptors];
-
- int phyAddr, /* PHY address */
- phyState, /* Last link state */
- phyOUI, /* Cached PHY type info */
- phyModel,
- phyRev;
-
- /* Statistics */
- uint32_t rxInterrupts;
- uint32_t rxOverrun;
- uint32_t rxRunt;
- uint32_t rxBadCRC;
- uint32_t rxNonOctet;
- uint32_t rxGiant;
-
- uint32_t txInterrupts;
-
- uint32_t txLostCarrier;
- uint32_t txDeferred;
- uint32_t txOneCollision;
- uint32_t txMultiCollision;
- uint32_t txTooManyCollision;
- uint32_t txLateCollision;
- uint32_t txUnderrun;
- uint32_t txPoorSignal;
-} EMACLocals;
-
-
-EMACLocals gEmacs[kMaxEMACs];
-
-int ppc405_emac_phy_adapt(EMACLocals* ep);
-
-/*----------------------------------- Globals --------------------------------------*/
-
-/*
- Pointers to the buffer descriptor tables used by the MAL. Tricky because they are both
- read and written to by the MAL, which is unaware of the CPU data cache. As four 8-byte
- descriptors fit into a single cache line this makes managing them in cached memory difficult.
- Best solution is to label them as uncached using the MMU. This code assumes an appropriate
- sized block stating at _enet_bdesc_base has been reserved by linkcmds and has been set up
- with uncached MMU attrributes in bspstart.c */
-
-LINKER_SYMBOL(_enet_bdesc_start); /* start of buffer descriptor space, from linkcmds */
-LINKER_SYMBOL(_enet_bdesc_end); /* top limit, from linkcmds */
-
-static MALDescriptor* gTx0Descs = NULL;
-static MALDescriptor* gRx0Descs = NULL;
-static MALDescriptor* gTx1Descs = NULL;
-static MALDescriptor* gRx1Descs = NULL;
-
-/*------------------------------------------------------------*/
-
-
-/*
- * RTEMS event used by interrupt handler to signal driver tasks.
- * This must not be any of the events used by the network task synchronization.
- */
-#define INTERRUPT_EVENT RTEMS_EVENT_1
-
-/*
- * RTEMS event used to start transmit daemon.
- * This must not be the same as INTERRUPT_EVENT.
- */
-#define START_TRANSMIT_EVENT RTEMS_EVENT_2
-
-#define _sync __asm__ volatile ("sync\n"::)
-
-#define kCacheLineMask (PPC_CACHE_ALIGNMENT - 1)
-
-
-/*----------------------- IRQ handler glue -----------------------*/
-
-static void InstallIRQHandler(rtems_irq_number id,
- rtems_irq_hdl handler,
- rtems_irq_enable turnOn,
- rtems_irq_disable turnOff)
-{
- rtems_irq_connect_data params;
-
- params.name = id;
- params.hdl = handler;
- params.on = turnOn;
- params.off = turnOff;
- params.isOn = NULL;
- params.handle = NULL;
- if (! BSP_install_rtems_irq_handler(&params))
- rtems_panic ("Can't install interrupt handler");
-}
-
-static void
-NoAction(const rtems_irq_connect_data* unused)
-{
- /* printf("NoAction %d\n",unused->name); */
-}
-
-
-/*------------------------ PHY interface -------------------------- */
-/* This code recognises and works with the 88E1111 only. Other PHYs
- will require appropriate adaptations to this code */
-
-enum {
- kPHYControl = 0,
- kPHYReset = 0x8000,
- kPHYStatus = 1,
- kPHYLinkStatus = 0x0004,
- kPHYID1 = 2,
- kPHYID2 = 3,
- kPHYAutoNegExp = 6,
- kPHY1000BaseTCtl = 9,
- kPHYExtStatus = 15,
-
- /* 88E1111 identification */
- kMarvellOUI = 0x5043,
- k88E1111Part = 0x0C,
-
- /* 88E1111 register addresses */
- k8PHYSpecStatus = 17,
- k8PHYSpeedShift = 14,
- k8PHYDuplex = 0x2000,
- k8PHYResolved = 0x0800,
- k8PHYLinkUp = 0x0400,
- k8IntStatus = 19,
- k8IntEnable = 18,
- k8AutoNegComplete = 0x0800,
- k8LinkStateChanged = 0x0400,
- k8ExtCtlReg = 20,
- k8RcvTimingDelay = 0x0080,
- k8XmtTimingDelay = 0x0002,
- k8XmtEnable = 0x0001,
- k8LEDCtlReg = 24,
- k8ExtStatusReg = 27,
-};
-
-
-static uint16_t ReadPHY(EMACLocals* ep, uint8_t reg)
-{
- int n = 0;
- uint32_t t;
-
- reg &= 0x1F;
-
- /* 405EX-specific! */
- while ((ep->EMAC->STAcontrol & keSTARun) != 0)
- { ; }
- ep->EMAC->STAcontrol = keSTADirectRd + (ep->phyAddr<<5) + reg;
- ep->EMAC->STAcontrol |= keSTARun;
- /* Wait for the read to complete, should take ~25usec */
- do {
- t = ep->EMAC->STAcontrol;
- if (++n > 200000)
- rtems_panic("PHY read timed out");
- } while ((t & keSTARun) != 0);
-
- if (t & kSTAErr)
- rtems_panic("PHY read failed");
- return t >> 16;
-}
-
-static void WritePHY(EMACLocals* ep, uint8_t reg, uint16_t value)
-{
-
- reg &= 0x1F;
-
- /* 405EX-specific */
- while ((ep->EMAC->STAcontrol & keSTARun) != 0)
- { ; }
- ep->EMAC->STAcontrol = (value<<16) | keSTADirectWr | (ep->phyAddr<<5) | reg;
- ep->EMAC->STAcontrol |= keSTARun;
-}
-
-static void ResetPHY(EMACLocals* ep)
-{
- int n;
-
- n = ReadPHY(ep, kPHYControl);
- n |= kPHYReset;
- WritePHY(ep, kPHYControl, n);
- do {
- rtems_task_wake_after( (rtems_bsdnet_ticks_per_second/20) + 1 );
- n = ReadPHY(ep, kPHYControl);
- } while ((n & kPHYReset)!=0);
-}
-
-enum {
- kELinkUp = 0x80,
- kELinkFullDuplex = 0x40,
- kELinkSpeed10 = 0,
- kELinkSpeed100 = 1,
- kELinkSpeed1000 = 2,
- kELinkSpeedMask = 3
-};
-
-static int GetPHYLinkState(EMACLocals* ep)
-/* Return link state (up/speed/duplex) as a set of flags */
-{
- int state, result;
-
- /* if (ep->phyOUI==kMarvellOUI) */
- result = 0;
- state = ReadPHY(ep,k8PHYSpecStatus);
- if ((state & k8PHYLinkUp) && (state & k8PHYResolved)) {
- result |= kELinkUp;
- if (state & k8PHYDuplex) result |= kELinkFullDuplex;
- result |= ((state >> k8PHYSpeedShift) & 3);
- }
- return result;
-}
-
-/*---------------------- PHY setup ------------------------*/
-
-
-static void InitPHY(EMACLocals* ep)
-{
- int id,id2,n;
-
- id = ReadPHY(ep,kPHYID1);
- id2 = ReadPHY(ep,kPHYID2);
- ep->phyOUI = (id<<6) + (id2>>10);
- ep->phyModel = (id2>>4) & 0x1F;
- ep->phyRev = id2 & 0xF;
-
- #ifdef qDebug
- printf("PHY %d maker $%X model %d revision %d\n",ep->phyAddr,ep->phyOUI,ep->phyModel,ep->phyRev);
- #endif
-
- /* Test for PHYs that we understand; insert new PHY types initialisation here */
- if (ep->phyOUI == kMarvellOUI || ep->phyModel == k88E1111Part) {
- /* 88E111-specific: Enable RxTx timing control, enable transmitter */
- n = ReadPHY(ep, k8ExtCtlReg);
- n |= k8RcvTimingDelay + k8XmtTimingDelay + k8XmtEnable;
- WritePHY(ep, k8ExtCtlReg, n);
-
- /* Set LED mode; Haleakala has LINK10 and TX LEDs only. Set up to do 100/1000 and link up/active*/
- WritePHY(ep, k8LEDCtlReg, 0x4109);
-
- /* Need to do a reset after fiddling with registers*/
- ResetPHY(ep);
- } else
- rtems_panic("Unknown PHY type");
-}
-
-
-/*--------------------- Interrupt handlers for the MAL ----------------------------- */
-
-static void
-MALTXDone_handler(rtems_irq_hdl_param param)
-{
- int n;
-
- n = PPC_DEVICE_CONTROL_REGISTER(MAL0_TXEOBISR);
- if (n & kMALChannel0) {
- gEmacs[0].txInterrupts++;
- rtems_event_send (gEmacs[0].txDaemonTid, INTERRUPT_EVENT);
- }
- if (n & kMALChannel1) {
- gEmacs[1].txInterrupts++;
- rtems_event_send (gEmacs[1].txDaemonTid, INTERRUPT_EVENT);
- }
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_TXEOBISR,n);
-}
-
-static void
-MALRXDone_handler (rtems_irq_hdl_param param)
-{
- int n;
-
- n = PPC_DEVICE_CONTROL_REGISTER(MAL0_RXEOBISR);
- if (n & kMALChannel0) {
- gEmacs[0].rxInterrupts++;
- rtems_event_send (gEmacs[0].rxDaemonTid, INTERRUPT_EVENT);
- }
- if (n & kMALChannel1) {
- gEmacs[1].rxInterrupts++;
- rtems_event_send (gEmacs[1].rxDaemonTid, INTERRUPT_EVENT);
- }
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_RXEOBISR,n); /* Write back to clear interrupt */
-}
-
-/* These handlers are useful for debugging, but we don't actually need to process these interrupts */
-
-static void
-MALErr_handler (rtems_irq_hdl_param param)
-{
- uint32_t errCause;
-
- errCause = PPC_DEVICE_CONTROL_REGISTER(MAL0_ESR);
- /* Clear the error */
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_ESR,errCause);
-}
-
-static void
-EMAC0Err_handler (rtems_irq_hdl_param param)
-{
- uint32_t errCause;
-
- errCause = gEmacs[0].EMAC->intStatus;
- /* Clear error by writing back */
- gEmacs[0].EMAC->intStatus = errCause;
-}
-
-static void
-EMAC1Err_handler (rtems_irq_hdl_param param)
-{
- uint32_t errCause;
-
- errCause = gEmacs[1].EMAC->intStatus;
- /* Clear error by writing back */
- gEmacs[1].EMAC->intStatus = errCause;
-}
-
-
-/*--------------------- Low-level hardware initialisation ----------------------------- */
-
-
-
-static void
-mal_initialise(void)
-{
- uint32_t bdescbase;
- int nBytes, ntables;
-
- /*------------------- Initialise the MAL for both channels ---------------------- */
-
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_CFG,kMALReset);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_TXCARR, kMALChannel0 | kMALChannel1);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_RXCARR, kMALChannel0 | kMALChannel1);
-
- /* Acquire MAL interrupts */
- InstallIRQHandler(BSP_UIC_MALTXEOB, MALTXDone_handler, NoAction, NoAction);
- InstallIRQHandler(BSP_UIC_MALRXEOB, MALRXDone_handler, NoAction, NoAction);
- InstallIRQHandler(BSP_UIC_MALSERR, MALErr_handler, NoAction, NoAction);
-
- /* Set up the buffer descriptor tables */
- bdescbase = (uint32_t)(_enet_bdesc_start);
- nBytes = sizeof(MALDescriptor) * 256;
- ntables = 4;
- if (get_ppc_cpu_type() != PPC_405EX) {
- /* The 405GP/GPr requires table bases to be 4K-aligned and can use two tx channels on one EMAC */
- nBytes = (nBytes + 0x0FFF) & ~0x0FFF;
- bdescbase = (bdescbase + 0x0FFF) & ~0x0FFF;
- ntables = 3;
- }
-
- /* printf("Buffer descriptors at $%X..$%X, code from $%X\n",bdescbase, bdescbase + nBytes*ntables - 1,(uint32_t)&_text_start); */
-
- /* Check that we have been given enough space and the buffers don't run past the enet_bdesc_end address */
- if (bdescbase + nBytes*ntables > (uint32_t)_enet_bdesc_end)
- rtems_panic("Ethernet descriptor space insufficient!");
-
- gTx0Descs = (MALDescriptor*)bdescbase;
- gTx1Descs = (MALDescriptor*)(bdescbase + nBytes);
- gRx0Descs = (MALDescriptor*)(bdescbase + nBytes*2);
- /* Clear the buffer descriptor tables */
- memset(gTx0Descs, 0, sizeof(MALDescriptor)*256);
- memset(gTx1Descs, 0, sizeof(MALDescriptor)*256);
- memset(gRx0Descs, 0, sizeof(MALDescriptor)*256);
- if (get_ppc_cpu_type() == PPC_405EX) {
- gRx1Descs = (MALDescriptor*)(bdescbase + nBytes*3);
- memset(gRx1Descs, 0, sizeof(MALDescriptor)*256);
- }
-
- /* Set up the MAL registers */
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_TXCTP0R,gTx0Descs);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_TXCTP1R,gTx1Descs);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_RXCTP0R,gRx0Descs);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_RCBS0, (MCLBYTES-16)>>4); /* The hardware writes directly to the mbuf clusters, so it can write MCLBYTES */
- if (get_ppc_cpu_type() == PPC_405EX) {
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_CFG,kMALMedHiPriority + keMALRdMaxBurst32 + keMALWrMedHiPriority + keMALWrMaxBurst32 +
- kMALLocksOPB + kMALLocksErrs + kMALCanBurst);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_RXCTP1R,gRx1Descs);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_RCBS1, (MCLBYTES-16)>>4);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_IER,0xF7); /* Enable all MAL interrupts */
- } else {
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_CFG,kMALMedHiPriority + kMALLocksOPB + kMALLocksErrs + kMALCanBurst + kMALLatency8);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_IER,0x1F); /* Enable all MAL interrupts */
- }
-}
-
-
-
-#ifdef qDebug
-static void printaddr(uint8_t* enetaddr)
-{
- printf("%02X.%02X.%02X.%02X.%02X.%02X",enetaddr[0],enetaddr[1],enetaddr[2],enetaddr[3],enetaddr[4],enetaddr[5]);
-}
-#endif
-
-static bool gMALInited = FALSE;
-
-static void
-ppc405_emac_initialize_hardware(EMACLocals* ep)
-{
-
- int n,mfr;
- int unitnum = ep->arpcom.ac_if.if_unit;
-
- if (get_ppc_cpu_type() == PPC_405EX) {
- /* PPC405EX: configure the RGMII bridge and clocks */
- RGMIIRegisters* rgmp = (RGMIIRegisters*)RGMIIAddress;
- rgmp->FER = 0x00080055; /* Both EMACs RGMII */
- rgmp->SSR = 0x00000044; /* Both EMACs 1000Mbps */
- /* Configure the TX clock to be external */
- mfsdr(SDR0_MFR,mfr);
- mfr &= ~0x0C000000; /* Switches both PHYs */
- mtsdr(SDR0_MFR,mfr);
- }
-
- /* Reset the EMAC */
- n = 0;
- ep->EMAC->mode0 = kEMACSoftRst;
- while ((ep->EMAC->mode0 & kEMACSoftRst) != 0)
- n++; /* Wait for it to complete */
-
- /* Set up so we can talk to the PHY */
- ep->EMAC->mode1 = keEMACIPHYAddr4 | keEMACOPB100MHz;
-
- /* Initialise the PHY */
- InitPHY(ep);
-
- /* Initialise the MAL (once only) */
- if ( ! gMALInited) {
- mal_initialise();
- gMALInited = TRUE;
- }
-
- /* Set up IRQ handlers and enable the MAL channels for this port */
- if (unitnum==0) {
- ep->xmtDescTable = gTx0Descs;
- ep->rcvDescTable = gRx0Descs;
- InstallIRQHandler(BSP_UIC_EMAC0, EMAC0Err_handler, NoAction, NoAction);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_TXCASR,kMALChannel0);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_RXCASR,kMALChannel0);
- } else {
- ep->xmtDescTable = gTx1Descs;
- ep->rcvDescTable = gRx1Descs;
- InstallIRQHandler(BSP_UIC_EMAC1, EMAC1Err_handler, NoAction, NoAction);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_TXCASR,kMALChannel1);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_RXCASR,kMALChannel1);
- }
-
- /* The rest of the EMAC initialisation is done in emac_phy_adapt
- when we know what the characteristics of the link are */
-}
-
-
-/* EMAC second stage initialisation; talks to the PHY to find out how to do it.
- Resets the EMAC if the PHY parameters need to be changed */
-
-int ppc405_emac_phy_adapt(EMACLocals* ep)
-{
- int linkState = GetPHYLinkState(ep);
- int spd;
-
- if ((linkState & kELinkUp) && (linkState != ep->phyState)) {
- /* Reset the EMAC and set registers according to PHY state */
- int i,n = 0;
- uint32_t mode, rmode;
-
- ep->EMAC->mode0 = kEMACSoftRst;
- while ((ep->EMAC->mode0 & kEMACSoftRst) != 0)
- n++; /* Wait for it to complete */
- spd = linkState & kELinkSpeedMask;
- mode = (spd<<22) | kgEMACTx0Multi;
- if (get_ppc_cpu_type() == PPC_405EX)
- mode |= (keEMAC16KRxFIFO | keEMAC16KTxFIFO | keEMACIPHYAddr4 | keEMACOPB100MHz );
- else
- mode |= (kgEMAC4KRxFIFO | kgEMAC2KTxFIFO);
- if (linkState & kELinkFullDuplex)
- mode |= kEMACFullDuplex + kEMACDoFlowControl;
- if ( (linkState & kELinkFullDuplex) || (spd > kELinkSpeed10) )
- mode |= kEMACIgnoreSQE;
-
-
- if (spd==kELinkSpeed1000) {
- /* Gigabit, so we support jumbo frames. Take appropriate measures: adjust the if_mtu */
- /* Note that we do this here because changing it later doesn't work very well : see
- the SIOCSIFMTU discussion below */
- struct ifnet* ifp = &ep->arpcom.ac_if;
- ifp->if_mtu = ETHERMTU_JUMBO;
- mode |= keEMACJumbo;
- }
-
-
- ep->phyState = linkState;
- ep->EMAC->mode1 = mode;
-
- /* Install 48-bit hardware address that we have been given */
- ep->EMAC->addrHi = (ep->arpcom.ac_enaddr[0]<<8) + ep->arpcom.ac_enaddr[1];
- ep->EMAC->addrLo = (ep->arpcom.ac_enaddr[2]<<24) + (ep->arpcom.ac_enaddr[3]<<16)
- + (ep->arpcom.ac_enaddr[4]<<8) + (ep->arpcom.ac_enaddr[5] );
-
- /* Set receive mode appropriately */
- rmode = kEMACStripPadding + kEMACStripFCS + kEMACBrcastRcv;
-
- if (ep->arpcom.ac_if.if_flags & IFF_PROMISC) rmode |= kEMACPromiscRcv;
- else rmode |= kEMACIndivRcv;
- if (get_ppc_cpu_type() == PPC_405EX)
- rmode |= keEMACRxFIFOAFMax;
- if ((ep->arpcom.ac_if.if_flags & IFF_ALLMULTI) != 0)
- rmode |= kEMACPromMultRcv;
- else if ((ep->arpcom.ac_if.if_flags & IFF_MULTICAST) != 0)
- rmode |= kEMACMultcastRcv;
-
- ep->EMAC->rcvMode = rmode;
-
- if (get_ppc_cpu_type() == PPC_405EX)
- for (i=0; i<8; i++)
- ep->EMAC->e_groupHash[i] = 0;
- else
- for (i=0; i<4; i++)
- ep->EMAC->g_groupHash[i] = 0;
-
- if (get_ppc_cpu_type() == PPC_405EX) {
- /* Rcv low watermark, must be < mode1 Rcv FIFO size and > MAL burst length (default 64x4 bytes), 16-byte units
- High watermark must be > low and < RcvFIFO size */
- ep->EMAC->rcvWatermarks = (16<<22) + (768<<6);
- /* Xmt low request must be >= 17 FIFO entries, Xmt urgent must be > low */
- ep->EMAC->xmtMode1 = (17<<27) + (68<<14); /* TLR = 17, TUR = 68 */
- /* Xmt partial packet request threshold */
- ep->EMAC->xmtReqThreshold = ((1000>>2)-1) << 24; /* TRTR[TRT] = 1000 FIFO entries */
- } else {
- ep->EMAC->rcvWatermarks = (15<<24) + (32<<8);
- ep->EMAC->xmtReqThreshold = ((1448>>6)-1) << 26; /* TRT = 1024b */
- ep->EMAC->xmtMode1 = 0x40200000; /* TLR = 8w=32b, TUR=32w=128b */
- }
-
- ep->EMAC->IPGap = 8;
-
- /* Want EMAC interrupts for error logging & statistics */
- ep->EMAC->intEnable = kEMACIOverrun + kEMACIPause + kEMACIBadPkt + kEMACIRuntPkt + kEMACIShortEvt
- + kEMACIAlignErr + kEMACIBadFCS + kEMACIOverSize + kEMACILLCRange + kEMACISQEErr
- + kEMACITxErr;
-
- /* Start it running */
- ep->EMAC->mode0 = kEMACRxEnable + kEMACTxEnable;
- return 0;
- } else
- return -1;
-}
-
-
-static void
-ppc405_emac_disable(EMACLocals* ep)
-/* Disable the EMAC channels so we stop running and processing interrupts */
-{
- ep->EMAC->mode0 = 0;
-}
-
-static void
-ppc405_emac_startxmt(EMACLocals* ep)
-/* Start the transmitter: set TMR0[GNP] */
-{
- ep->EMAC->xmtMode0 = kEMACNewPacket0 + 7; /* *** TFAE value for EX */
-}
-
-static void ppc405_emac_watchdog(struct ifnet* ifp)
-/* Called if a transmit stalls or when the interface is down. Check the PHY
- until we get a valid link */
-{
- EMACLocals* ep = ifp->if_softc;
-
- if (ppc405_emac_phy_adapt(ep)==0) {
- ep->arpcom.ac_if.if_flags |= IFF_RUNNING;
- ifp->if_timer = 0; /* No longer needed */
- } else
- ifp->if_timer = 1; /* reschedule, once a second */
-}
-
-
-
-/*----------------------- The transmit daemon/task -------------------------- */
-
-
-static void
-FreeTxDescriptors(EMACLocals* ep)
-/* Make descriptors and mbufs at xmtBusyIndex available for re-use if the packet that they */
-/* point at has all gone */
-{
- uint16_t scan, status;
-
- if (ep->xmtBusyIndex != kNoXmtBusy) {
- scan = ep->xmtBusyIndex;
- while (TRUE) {
- /* Scan forward through the descriptors */
- status = ep->xmtDescTable[scan].ctrlBits;
- if (++scan >= kNXmtDescriptors)
- scan = 0;
- /* If we find a ready (i.e not-yet-sent) descriptor, stop */
- if ((status & kMALTxReady) != 0)
- break;
- /* If we find a last descriptor, we can free all the buffers up to and including it */
- if ((status & kMALLast) != 0) {
- /* End of packet and it has been sent or abandoned; advance xmtBusyIndex to */
- /* the next buffer and free buffers. */
- if ((status & kEMACErrMask) != 0) {
- /* Transmit error of some kind */
-
- if ((status & kEMACDeferred) != 0)
- ep->txDeferred++;
- if ((status & kEMACLostCarrier) != 0)
- ep->txLostCarrier++; /* *** Perhaps more serious reaction needed... */
-
- if ((status & kEMACLateColl) != 0)
- ep->txLateCollision++;
- if ((status & kEMACOneColl) != 0)
- ep->txOneCollision++;
- if ((status & kEMACMultColl) != 0)
- ep->txMultiCollision++;
- if ((status & kEMACCollFail) != 0)
- ep->txTooManyCollision++;
-
- if ((status & kEMACSQEFail) != 0)
- ep->txPoorSignal++;
- if ((status & kEMACUnderrun) != 0)
- ep->txUnderrun++;
- }
- while (ep->xmtBusyIndex != scan) {
- m_free(ep->txMBufs[ep->xmtBusyIndex]);
- if (++ep->xmtBusyIndex >= kNXmtDescriptors) ep->xmtBusyIndex = 0;
- }
- if (ep->xmtBusyIndex == ep->xmtFreeIndex) {
- /* Nothing is busy */
- ep->xmtBusyIndex = kNoXmtBusy;
- break;
- }
- }
- }
- }
-}
-
-
-static void
-SendPacket (EMACLocals* ep, struct ifnet *ifp, struct mbuf *m)
-/* Given a chain of mbufs, set up a transmit description and fire it off */
-{
- int nAdded, index, lastidx = -1, totalbytes;
- uint16_t status;
- struct mbuf* lastAdded;
-
- nAdded = 0;
- totalbytes = 0;
- lastAdded = NULL;
- index = ep->xmtFreeIndex;
-
- /* Go through the chain of mbufs setting up descriptors for each */
- while (m != NULL) {
-
- if (m->m_len == 0) {
- /* Can be empty: dispose and unlink from chain */
- m = m_free(m);
- if (lastAdded!=NULL) lastAdded->m_next = m;
- } else {
- /* Make sure the mbuf has been written to memory */
- rtems_cache_flush_multiple_data_lines(mtod (m, void *), m->m_len);
- /* If there are no descriptors available wait until there are */
- while (index == ep->xmtBusyIndex) {
- rtems_event_set events;
- ifp->if_timer = 2;
- /* Then check for free descriptors, followed by: */
- rtems_bsdnet_event_receive (INTERRUPT_EVENT, RTEMS_WAIT | RTEMS_EVENT_ANY, RTEMS_NO_TIMEOUT, &events);
- FreeTxDescriptors(ep);
- }
-
- /* Fill in a descriptor for this mbuf and record it */
- ep->txMBufs[index] = m;
- ep->xmtDescTable[index].ptr = mtod (m, void *);
- ep->xmtDescTable[index].adrDataSize = m->m_len;
- /* Fill in ctrlBits as we go but don't mark the first one as ready yet */
- status = kEMACGenFCS + kEMACGenPad + kEMACRepSrcAddr;
- if (nAdded > 0)
- status |= kMALTxReady;
- if (index==kNXmtDescriptors-1)
- status |= kMALWrap;
- ep->xmtDescTable[index].ctrlBits = status;
- lastidx = index;
-
- totalbytes += m->m_len;
- lastAdded = m;
- m = m->m_next;
- nAdded++;
-
- index += 1;
- if (index==kNXmtDescriptors)
- index = 0;
-
- if (nAdded==kNXmtDescriptors)
- rtems_fatal_error_occurred(RTEMS_INTERNAL_ERROR); /* This is impossible, of course... */
- }
- }
-
- _Assert( lastidx != -1 );
-
- if (nAdded > 0) {
- /* Done and we added some buffers */
- /* Label the last buffer and ask for an interrupt */
- ep->xmtDescTable[lastidx].ctrlBits |= kMALLast + kMALInterrupt;
- /* Finally set the ready bit on the first buffer */
- ep->xmtDescTable[ep->xmtFreeIndex].ctrlBits |= kMALTxReady;
- /* Make sure this has been written */
- _sync;
- if (ep->xmtBusyIndex == kNoXmtBusy)
- ep->xmtBusyIndex = ep->xmtFreeIndex;
- ep->xmtFreeIndex = index;
- /* Poke the EMAC to get it started (which may not be needed if its already running */
- ppc405_emac_startxmt(ep);
- ifp->if_timer = 2;
- }
-}
-
-static void
-ppc405_emac_txDaemon (void* param)
-{
- EMACLocals* ep = param;
- struct ifnet *ifp = &ep->arpcom.ac_if;
- struct mbuf *m;
- rtems_event_set events;
-
- ep->xmtFreeIndex = 0;
- ep->xmtBusyIndex = kNoXmtBusy;
- while (TRUE) {
- /* Wait for someone wanting to transmit */
- rtems_bsdnet_event_receive (START_TRANSMIT_EVENT | INTERRUPT_EVENT,
- RTEMS_EVENT_ANY | RTEMS_WAIT,
- RTEMS_NO_TIMEOUT,
- &events);
- if (events & INTERRUPT_EVENT)
- ifp->if_timer = 0;
- /* Grab packets and send until empty */
- /* Note that this doesn't (at the time of writing, RTEMS 4.9.1), ever get asked to send more than
- one header mbuf and one data mbuf cluster, regardless of the MTU. This is because sosend() in the FreeBSD
- stack only passes one mbuf at a time across to tcp_send, which immediately sends it */
- while (TRUE) {
- FreeTxDescriptors(ep);
- IF_DEQUEUE(&ifp->if_snd, m);
- if (m == NULL)
- break;
- SendPacket (ep, ifp, m);
- }
- ifp->if_flags &= ~IFF_OACTIVE;
- }
-}
-
-/*----------------------- The receive daemon/task -------------------------- */
-
-static void
-MakeRxBuffer(EMACLocals* ep, int index)
-{
- struct mbuf* m;
-
- /* Allocate an mbuf, wait if necessary, label as dynamic data, start of record */
- MGETHDR (m, M_WAIT, MT_DATA);
- /* Allocate a cluster buffer to this mbuf, waiting if necessary */
- MCLGET (m, M_WAIT);
- /* Set up reference to the interface the packet will be received on */
- m->m_pkthdr.rcvif = &ep->arpcom.ac_if;
- ep->rxMBufs[index] = m;
- ep->rcvDescTable[index].ptr = mtod (m, uint8_t*);
- ep->rcvDescTable[index].adrDataSize = 0x0EEE; /* Precaution */
- if (index==ep->nRxBuffers-1)
- ep->rcvDescTable[index].ctrlBits = kMALRxEmpty + kMALInterrupt + kMALWrap;
- else
- ep->rcvDescTable[index].ctrlBits = kMALRxEmpty + kMALInterrupt;
-}
-
-
-
-static void
-ppc405_emac_rxDaemon (void* param)
-{
- EMACLocals* ep = param;
- int index,n,mdex;
- struct mbuf* m;
- struct mbuf* mstart = NULL;
- struct mbuf* mlast = NULL;
- struct ifnet* ifp;
- struct ether_header* eh = NULL;
- rtems_event_set events;
-
- /* Startup : allocate a bunch of receive buffers and point the descriptor table entries at them */
- ifp = &ep->arpcom.ac_if;
- index = 0;
- while (index < ep->nRxBuffers) {
- MakeRxBuffer(ep,index);
- index += 1;
- }
- index = 0;
- mdex = 0;
-
- /* Loop waiting for frames to arrive */
- while (TRUE) {
- rtems_bsdnet_event_receive (INTERRUPT_EVENT,
- RTEMS_WAIT | RTEMS_EVENT_ANY,
- RTEMS_NO_TIMEOUT,
- &events);
- while ((ep->rcvDescTable[index].ctrlBits & kMALRxEmpty) == 0) {
- /* Got a frame */
- uint16_t flags = ep->rcvDescTable[index].ctrlBits;
- if ((flags & kEMACErrMask) != 0) {
- /* It has errors. Update statistics */
- if ((flags & kEMACOverrun) != 0)
- ep->rxOverrun++;
- if ((flags & kEMACRuntPkt) != 0)
- ep->rxRunt++;
- if ((flags & kEMACBadFCS) != 0)
- ep->rxBadCRC++;
- if ((flags & kEMACAlignErr) != 0)
- ep->rxNonOctet++;
- if ((flags & kEMACPktLong) != 0)
- ep->rxGiant++;
- /* and reset descriptor to empty */
-
- /* No need to get new mbufs, just reset */
- ep->rcvDescTable[index].adrDataSize = 0x0EEE;
- if (index==ep->nRxBuffers-1)
- ep->rcvDescTable[index].ctrlBits = kMALRxEmpty + kMALInterrupt + kMALWrap;
- else
- ep->rcvDescTable[index].ctrlBits = kMALRxEmpty + kMALInterrupt;
-
- } else {
- /* Seems to be OK. Invalidate cache over the size we received */
- n = ep->rcvDescTable[index].adrDataSize & 0x0FFF;
- m = ep->rxMBufs[index];
- rtems_cache_invalidate_multiple_data_lines(m->m_data, (n + kCacheLineMask) & ~kCacheLineMask);
-
- /* Consider copying small packets out of the cluster into m_pktdat to save clusters? */
- m->m_len = n;
-
- /* Jumbo packets will span multiple mbufs; chain them together and submit when we get the last one */
- if (flags & kMALRxFirst) {
- /* First mbuf in the packet */
- if (mstart!=NULL)
- rtems_panic("first, no last");
-
- /* Adjust the mbuf pointers to skip the header and set eh to point to it */
- m->m_len -= sizeof(struct ether_header);
- m->m_pkthdr.len = m->m_len;
- eh = mtod (m, struct ether_header *);
- m->m_data += sizeof(struct ether_header);
- mstart = m;
- mlast = m;
- mdex = index;
- } else {
- /* Chain onto mstart: add length to pkthdr.len */
- if (mstart == NULL)
- rtems_panic("last, no first");
-
- mstart->m_pkthdr.len += n;
- m->m_flags &= ~M_PKTHDR;
- mlast->m_next = m;
- mlast = m;
- }
-
- if (flags & kMALLast) {
- /* Last mbuf in the packet: pass base of the chain to a higher level */
- ether_input (ifp, eh, mstart);
-
- /* ether_input took the chain, set up new mbufs in the slots we used */
- mdex -= 1;
- do {
- if (++mdex==ep->nRxBuffers) mdex = 0;
- MakeRxBuffer(ep,mdex);
- } while (mdex != index);
- mstart = NULL;
- mlast = NULL;
- eh = NULL;
- }
- }
- index += 1;
- if (index == ep->nRxBuffers) index = 0;
- }
- }
-}
-
-/*----------- Vectored routines called through the driver struct ------------------ */
-
-static void ppc405_emac_init (void* p)
-/* Initialise the hardware, create and start the transmit and receive tasks */
-{
- char txName[] = "ETx0";
- char rxName[] = "ERx0";
-
- EMACLocals* ep = (EMACLocals*)p;
- if (ep->txDaemonTid == 0) {
- ppc405_emac_initialize_hardware(ep);
- rxName[3] += ep->phyAddr;
- ep->rxDaemonTid = rtems_bsdnet_newproc (rxName, 4096, ppc405_emac_rxDaemon, ep);
- txName[3] += ep->phyAddr;
- ep->txDaemonTid = rtems_bsdnet_newproc (txName, 4096, ppc405_emac_txDaemon, ep);
- }
- /* Only set IFF_RUNNING if the PHY is ready. If not set the watchdog timer running so we check it */
- if ( GetPHYLinkState(ep) & kELinkUp )
- ep->arpcom.ac_if.if_flags |= IFF_RUNNING;
- else
- ep->arpcom.ac_if.if_timer = 1;
-}
-
-static void ppc405_emac_start(struct ifnet *ifp)
-/* Send a packet: send an event to the transmit task, waking it up */
-{
- EMACLocals* ep = ifp->if_softc;
- rtems_event_send (ep->txDaemonTid, START_TRANSMIT_EVENT);
- ifp->if_flags |= IFF_OACTIVE;
-}
-
-static void ppc405_emac_stop (EMACLocals* ep)
-{
- uint32_t mask;
-
- mask = 0x80000000 >> ep->arpcom.ac_if.if_unit;
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_TXCARR,mask);
- PPC_SET_DEVICE_CONTROL_REGISTER(MAL0_RXCARR,mask);
- ppc405_emac_disable(ep);
- /* *** delete daemons, or do they exit themselves? */
- ep->arpcom.ac_if.if_flags &= ~IFF_RUNNING;
-}
-
-#ifdef qDebug
-static void ppc405_emac_stats (EMACLocals* ep)
-{
-
- printf (" Rx Interrupts:%-8lu", ep->rxInterrupts);
- printf (" Giant:%-8lu", ep->rxGiant);
- printf (" Runt:%-8lu\n", ep->rxRunt);
- printf (" Non-octet:%-8lu", ep->rxNonOctet);
- printf (" Bad CRC:%-8lu", ep->rxBadCRC);
- printf (" Overrun:%-8lu\n", ep->rxOverrun);
-
- printf (" Tx Interrupts:%-8lu", ep->txInterrupts);
- printf (" Long deferral:%-8lu", ep->txDeferred);
- printf (" No Carrier:%-8lu\n", ep->txLostCarrier);
- printf (" Late collision:%-8lu", ep->txLateCollision);
- printf (" One collision:%-8lu", ep->txOneCollision);
- printf (" Many collisions:%-8lu\n", ep->txMultiCollision);
- printf ("Excess collisions:%-8lu", ep->txTooManyCollision);
- printf (" Underrun:%-8lu", ep->txUnderrun);
- printf (" Poor signal:%-8lu\n", ep->txPoorSignal);
-}
-#endif
-
-static int UpdateMulticast(EMACLocals* ep)
-{
- /* Traverse list of multicast addresses and update hardware hash filter. This is just a work-reduction */
- /* step; the filter uses a hash of the hardware address and therefore doesn't catch all unwanted packets */
- /* We have to do other checks in software. */
- /* 405GP/GPr has 4x16-bit hash registers, 405EX/EXr has 8x32-bit */
-
- struct ether_multi* enm;
- struct ether_multistep step;
- uint32_t hash;
-
- #ifdef qMultiDebug
- printf("\nMulticast etheraddrs:\n");
- #endif
-
- ETHER_FIRST_MULTI(step, &ep->arpcom, enm);
- while (enm != NULL) {
-
- /* *** Doesn't implement ranges */
-
- hash = ether_crc32_be( (uint8_t*)&enm->enm_addrlo, sizeof(enm->enm_addrlo) );
- if (get_ppc_cpu_type() == PPC_405EX) {
- hash >>= 24; /* Upper 8 bits, split 3/5 */
- /* This has been experimentally verified against the hardware */
- ep->EMAC->e_groupHash[7-(hash>>5)] |= (1 << (hash & 0x1F));
- } else {
- hash >>= 26; /* Upper 6 bits, split 2/4 */
- /* This has not been checked */
- ep->EMAC->g_groupHash[3-(hash>>6)] |= (1 << (hash & 0xF));
- }
-
- #ifdef qMultiDebug
- printf(" ");
- printaddr(enm->enm_addrlo);
- printf(" = bit %d",hash);
- if (memcmp(&enm->enm_addrlo, &enm->enm_addrhi, 6) != 0) {
- printf(" - ");
- printaddr(enm->enm_addrhi);
- printf(" [not supported]");
- }
- printf("\n");
- #endif
-
- ETHER_NEXT_MULTI(step, enm);
- }
- #ifdef qMultiDebug
- {
- int i;
- printf(" Grouphash is ");
- for (i=0; i<8; i++)
- printf("%08X:",(int)ep->EMAC->e_groupHash[i]);
- printf("\n");
- }
- #endif
- return 0;
-}
-
-
-static int ppc405_emac_ioctl (struct ifnet *ifp, ioctl_command_t command, caddr_t data)
-{
- int error = 0;
- EMACLocals* ep = ifp->if_softc;
- struct ifreq* reqP = (struct ifreq *) data;
-
- switch (command) {
-
- case SIOCSIFFLAGS:
- switch (ifp->if_flags & (IFF_UP | IFF_RUNNING)) {
- case IFF_RUNNING:
- ppc405_emac_stop(ep);
- break;
-
- case IFF_UP:
- ppc405_emac_init(ep);
- break;
-
- case IFF_UP | IFF_RUNNING:
- ppc405_emac_stop(ep);
- ppc405_emac_init(ep);
- break;
-
- default:
- break;
- }
- break;
-
- case SIOCADDMULTI: {
- error = ether_addmulti( reqP, &ep->arpcom);
- if (error==ENETRESET)
- error = UpdateMulticast(ep);
- } break;
-
- case SIOCDELMULTI:
- error = ether_delmulti( (struct ifreq *) data, &ep->arpcom);
- if (error==ENETRESET)
- error = UpdateMulticast(ep);
- break;
-
- case SIOCSIFMTU: {
- /* Note that this may not do what you want; setting the interface MTU doesn't touch the route MTUs,
- and new routes are sometimes made by cloning old ones. So this won't change the MTU to known hosts
- and may not change the MTU to new ones either... */
- int max;
- if ( get_ppc_cpu_type() == PPC_405EX && (ep->EMAC->mode1 & keEMACJumbo) != 0 )
- max = ETHER_MAX_LEN_JUMBO;
- else
- max = ETHER_MAX_LEN;
- if (reqP->ifr_mtu > max - ETHER_HDR_LEN - ETHER_CRC_LEN)
- error = EINVAL;
- else
- ifp->if_mtu = reqP->ifr_mtu;
- } break;
-
- case SIO_RTEMS_SHOW_STATS:
- #ifdef qDebug
- ppc405_emac_stats(ep);
- #endif
- break;
-
- default:
- /* Not handled here, pass to generic */
- error = ether_ioctl(ifp,command,data);
- break;
- }
-
- #ifdef qDebug
- if (error != 0)
- printf("--- Ethernet ioctl %d failed %d\n",(int)command,error);
- #endif
-
- return error;
-}
-
-
-/*----------------------- External attach function --------------------------
- *
- * This is the one function we are required to define in here: declared in bsp.h
- * as RTEMS_BSP_NETWORK_DRIVER_ATTACH and called from rtems_bsdnet_attach
- *
-*/
-
-int
-rtems_emac_driver_attach(struct rtems_bsdnet_ifconfig* config, int attaching)
-{
- int unitNumber, nUnits;
- char* unitName;
- struct ifnet* ifp;
- EMACLocals* ep;
-
- if (attaching==0) {
- printk ("EMAC: driver cannot be detached.\n");
- return 0;
- }
-
- nUnits = 1;
- if (get_ppc_cpu_type()==PPC_405EX && get_ppc_cpu_revision() > 0x1474)
- nUnits = 2; /* PPC405EX has two interfaces, EXr has one */
-
- unitNumber = rtems_bsdnet_parse_driver_name (config, &unitName);
- if (unitNumber < 0 || unitNumber > nUnits-1) {
- printk ("EMAC: bad unit number %d.\n",unitNumber);
- return 0;
- }
-
- ep = &gEmacs[unitNumber];
-
- if (get_ppc_cpu_type()==PPC_405EX) {
- if (unitNumber==0) ep->EMAC = (EthernetRegisters_EX*)EMAC0EXAddress;
- else ep->EMAC = (EthernetRegisters_GP*)EMAC1EXAddress;
- } else
- ep->EMAC = (EthernetRegisters_GP*)EMAC0GPAddress;
-
- ifp = &ep->arpcom.ac_if;
- if (ifp->if_softc != NULL) {
- printk ("EMAC: driver already in use.\n");
- return 0;
- }
- ifp->if_softc = ep;
-
- if (config->hardware_address == 0)
- rtems_panic("No Ethernet MAC address specified!");
- memcpy (ep->arpcom.ac_enaddr, config->hardware_address, ETHER_ADDR_LEN);
-
- ifp->if_name = unitName;
- ifp->if_unit = unitNumber;
-
- if (config->mtu != 0)
- ifp->if_mtu = config->mtu;
- else
- ifp->if_mtu = ETHERMTU; /* May be adjusted later by ppc405_emac_phy_adapt() */
-
- ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
- if (ifp->if_snd.ifq_maxlen == 0)
- ifp->if_snd.ifq_maxlen = ifqmaxlen;
- ifp->if_init = &ppc405_emac_init;
- ifp->if_ioctl = ppc405_emac_ioctl;
- ifp->if_start = ppc405_emac_start;
- ifp->if_output = ether_output;
- ifp->if_watchdog = ppc405_emac_watchdog;
- ifp->if_timer = 0;
-
- if (config->rbuf_count != 0) {
- if (config->rbuf_count > 256) ep->nRxBuffers = 256;
- else ep->nRxBuffers = config->rbuf_count;
- } else
- ep->nRxBuffers = nmbclusters/2;
-
- ep->phyAddr = unitNumber+1;
- ep->phyState = 0;
-
- #ifdef qDebug
- printf("\n Setting up EMAC %d of %d\n",unitNumber+1,nUnits);
- printf(" MAC address is ");
- printaddr(ep->arpcom.ac_enaddr);
- printf(" MHLEN = %d, MINCLSIZE = %d MCLBYTES = %d\n",MHLEN,MINCLSIZE,MCLBYTES);
- printf(" ticks/sec = %d, usec/tick = %d\n", rtems_bsdnet_ticks_per_second, rtems_bsdnet_microseconds_per_tick);
- #endif
-
- if_attach (ifp);
- ether_ifattach (ifp);
-
- return 1;
-}
-