freebsd-dev/sys/boot/arm/at91/libat91/emac.c
Warner Losh ae78672c56 MFp4 (checkpoint of work in progress):
o Use a directory layout that is more akin to the i386 boot layout.
o Create a libat91 for library routines that are used by one or more
  of the boot loaders.
o Create bootiic for booting from an iic part.
o Create bootspi for booting from an spi part.
o Optimize the size of many of these routines (especially emac.c).  Except
  for the emac.c optimizations, all these have been tested.
o eliminate the inc directory, libat91 superceeds it.
o Move linker.cfg up a layer to allow it to be shared.
2006-04-19 17:16:49 +00:00

666 lines
15 KiB
C

/******************************************************************************
*
* Filename: emac.c
*
* Instantiation of routines for MAC/ethernet functions supporting tftp.
*
* Revision information:
*
* 28AUG2004 kb_admin initial creation
* 08JAN2005 kb_admin added tftp download
* also adapted from external sources
*
* BEGIN_KBDD_BLOCK
* No warranty, expressed or implied, is included with this software. It is
* provided "AS IS" and no warranty of any kind including statutory or aspects
* relating to merchantability or fitness for any purpose is provided. All
* intellectual property rights of others is maintained with the respective
* owners. This software is not copyrighted and is intended for reference
* only.
* END_BLOCK
*
* $FreeBSD$
*****************************************************************************/
#include "at91rm9200.h"
#include "emac.h"
#include "p_string.h"
#include "at91rm9200_lowlevel.h"
#include "lib.h"
/******************************* GLOBALS *************************************/
/*********************** PRIVATE FUNCTIONS/DATA ******************************/
static unsigned localMACSet, serverMACSet, MAC_init;
static unsigned char localMACAddr[6], serverMACAddr[6];
static unsigned localIPAddr, serverIPAddr;
static unsigned short serverPort, localPort;
static int ackBlock;
static unsigned lastAddress, lastSize;
static char *dlAddress;
static unsigned transmitBuffer[1024 / sizeof(unsigned)];
static unsigned tftpSendPacket[256 / sizeof(unsigned)];
receive_descriptor_t *p_rxBD;
/*
* .KB_C_FN_DEFINITION_START
* unsigned short IP_checksum(unsigned short *p, int len)
* This private function calculates the IP checksum for various headers.
* .KB_C_FN_DEFINITION_END
*/
static unsigned short
IP_checksum(void *cp, int len)
{
unsigned i, t;
unsigned short *p = (unsigned short *)cp;
len &= ~1;
for (i=0,t=0; i<len; i+=2, ++p)
t += SWAP16(*p);
t = (t & 0xffff) + (t >> 16);
return (~t);
}
/*
* .KB_C_FN_DEFINITION_START
* void GetServerAddress(void)
* This private function sends an ARP request to determine the server MAC.
* .KB_C_FN_DEFINITION_END
*/
static void
GetServerAddress(void)
{
arp_header_t *p_ARP;
p_ARP = (arp_header_t*)transmitBuffer;
p_memset((char*)p_ARP->dest_mac, 0xFF, 6);
p_memcpy((char*)p_ARP->src_mac, (char*)localMACAddr, 6);
p_ARP->frame_type = SWAP16(PROTOCOL_ARP);
p_ARP->hard_type = SWAP16(1);
p_ARP->prot_type = SWAP16(PROTOCOL_IP);
p_ARP->hard_size = 6;
p_ARP->prot_size = 4;
p_ARP->operation = SWAP16(ARP_REQUEST);
p_memcpy((char*)p_ARP->sender_mac, (char*)localMACAddr, 6);
p_memcpy((char*)p_ARP->sender_ip, (char*)localIPAddr, 4);
p_memset((char*)p_ARP->target_mac, 0, 6);
p_memcpy((char*)p_ARP->target_ip, (char*)serverIPAddr, 4);
// wait until transmit is available
while (!(*AT91C_EMAC_TSR & AT91C_EMAC_BNQ)) ;
*AT91C_EMAC_TSR |= AT91C_EMAC_COMP;
*AT91C_EMAC_TAR = (unsigned)transmitBuffer;
*AT91C_EMAC_TCR = 0x40;
}
/*
* .KB_C_FN_DEFINITION_START
* void Send_TFTP_Packet(char *tftpData, unsigned tftpLength)
* This private function initializes and send a TFTP packet.
* .KB_C_FN_DEFINITION_END
*/
static void
Send_TFTP_Packet(char *tftpData, unsigned tftpLength)
{
transmit_header_t *macHdr = (transmit_header_t*)tftpSendPacket;
ip_header_t *ipHdr;
udp_header_t *udpHdr;
unsigned t_checksum;
p_memcpy((char*)macHdr->dest_mac, (char*)serverMACAddr, 6);
p_memcpy((char*)macHdr->src_mac, (char*)localMACAddr, 6);
macHdr->proto_mac = SWAP16(PROTOCOL_IP);
ipHdr = &macHdr->iphdr;
ipHdr->ip_v_hl = 0x45;
ipHdr->ip_tos = 0;
ipHdr->ip_len = SWAP16(28 + tftpLength);
ipHdr->ip_id = 0;
ipHdr->ip_off = SWAP16(0x4000);
ipHdr->ip_ttl = 64;
ipHdr->ip_p = PROTOCOL_UDP;
ipHdr->ip_sum = 0;
p_memcpy((char*)ipHdr->ip_src, (char*)localIPAddr, 4);
p_memcpy((char*)ipHdr->ip_dst, (char*)serverIPAddr, 4);
ipHdr->ip_sum = SWAP16(IP_checksum(ipHdr, 20));
udpHdr = (udp_header_t*)(ipHdr + 1);
udpHdr->src_port = localPort;
udpHdr->dst_port = serverPort;
udpHdr->udp_len = SWAP16(8 + tftpLength);
udpHdr->udp_cksum = 0;
p_memcpy((char*)udpHdr+8, tftpData, tftpLength);
t_checksum = IP_checksum((char *)ipHdr + 12, (16 + tftpLength));
t_checksum = (~t_checksum) & 0xFFFF;
t_checksum += 25 + tftpLength;
t_checksum = (t_checksum & 0xffff) + (t_checksum >> 16);
t_checksum = (~t_checksum) & 0xFFFF;
udpHdr->udp_cksum = SWAP16(t_checksum);
while (!(*AT91C_EMAC_TSR & AT91C_EMAC_BNQ)) ;
*AT91C_EMAC_TSR |= AT91C_EMAC_COMP;
*AT91C_EMAC_TAR = (unsigned)tftpSendPacket;
*AT91C_EMAC_TCR = 42 + tftpLength;
}
/*
* .KB_C_FN_DEFINITION_START
* void TFTP_RequestFile(char *filename)
* This private function sends a RRQ packet to the server.
* .KB_C_FN_DEFINITION_END
*/
static void
TFTP_RequestFile(char *filename)
{
tftp_header_t tftpHeader;
char *cPtr, *ePtr, *mPtr;
unsigned length;
tftpHeader.opcode = SWAP16(TFTP_RRQ_OPCODE);
cPtr = (char*)&(tftpHeader.block_num);
ePtr = p_strcpy(cPtr, filename);
mPtr = p_strcpy(ePtr, "octet");
length = mPtr - cPtr;
length += 2;
Send_TFTP_Packet((char*)&tftpHeader, length);
}
/*
* .KB_C_FN_DEFINITION_START
* void TFTP_ACK_Data(char *data, unsigned short block_num, unsigned short len)
* This private function sends an ACK packet to the server.
* .KB_C_FN_DEFINITION_END
*/
static void
TFTP_ACK_Data(char *data, unsigned short block_num, unsigned short len)
{
tftp_header_t tftpHeader;
if (block_num == SWAP16(ackBlock + 1)) {
++ackBlock;
p_memcpy(dlAddress, data, len);
dlAddress += len;
lastSize += len;
}
tftpHeader.opcode = SWAP16(TFTP_ACK_OPCODE);
tftpHeader.block_num = block_num;
Send_TFTP_Packet((char*)&tftpHeader, 4);
if (len < 512)
ackBlock = -2;
}
/*
* .KB_C_FN_DEFINITION_START
* void CheckForNewPacket(ip_header_t *pHeader)
* This private function polls for received ethernet packets and handles
* any here.
* .KB_C_FN_DEFINITION_END
*/
static void
CheckForNewPacket(void)
{
unsigned short *pFrameType;
unsigned i;
char *pData;
ip_header_t *pIpHeader;
arp_header_t *p_ARP;
int process = 0;
process = 0;
for (i = 0; i < MAX_RX_PACKETS; ++i) {
if(p_rxBD[i].address & 0x1) {
process = 1;
(*AT91C_EMAC_RSR) |= (*AT91C_EMAC_RSR);
break;
}
}
if (!process)
return ;
process = i;
pFrameType = (unsigned short *) ((p_rxBD[i].address & 0xFFFFFFFC) + 12);
pData = (char *)(p_rxBD[i].address & 0xFFFFFFFC);
switch (SWAP16(*pFrameType)) {
case PROTOCOL_ARP:
p_ARP = (arp_header_t*)pData;
i = SWAP16(p_ARP->operation);
if (i == ARP_REPLY) {
// check if new server info is available
if ((!serverMACSet) &&
(!(p_memcmp((char*)p_ARP->sender_ip,
(char*)serverIPAddr, 4)))) {
serverMACSet = 1;
p_memcpy((char*)serverMACAddr,
(char*)p_ARP->sender_mac, 6);
}
} else if (i == ARP_REQUEST) {
// ARP REPLY operation
p_ARP->operation = SWAP16(ARP_REPLY);
// Swap the src/dst MAC addr
p_memcpy(p_ARP->dest_mac, p_ARP->src_mac, 6);
p_memcpy(p_ARP->src_mac, localMACAddr, 6);
// Do IP and MAC addr at same time.
p_memcpy(p_ARP->target_mac, p_ARP->sender_mac, 10);
p_memcpy(p_ARP->sender_mac, localMACAddr, 6);
p_memcpy(p_ARP->sender_ip, (char *)&localIPAddr, 4);
if (!(*AT91C_EMAC_TSR & AT91C_EMAC_BNQ))
break;
*AT91C_EMAC_TSR |= AT91C_EMAC_COMP;
*AT91C_EMAC_TAR = (unsigned)pData;
*AT91C_EMAC_TCR = 0x40;
}
break;
case PROTOCOL_IP:
pIpHeader = (ip_header_t*)(pData + 14);
switch(pIpHeader->ip_p) {
case PROTOCOL_UDP:
{
udp_header_t *udpHdr;
tftp_header_t *tftpHdr;
udpHdr = (udp_header_t*)((char*)pIpHeader+20);
tftpHdr = (tftp_header_t*)((char*)udpHdr + 8);
if (udpHdr->dst_port != localPort)
break;
if (tftpHdr->opcode != SWAP16(TFTP_DATA_OPCODE))
break;
if (ackBlock == -1) {
if (tftpHdr->block_num != SWAP16(1))
break;
serverPort = udpHdr->src_port;
ackBlock = 0;
}
if (serverPort != udpHdr->src_port)
break;
TFTP_ACK_Data(tftpHdr->data,
tftpHdr->block_num,
SWAP16(udpHdr->udp_len) - 12);
}
break;
default:
break;
}
break;
default:
break;
}
p_rxBD[process].address &= ~0x01;
}
/*
* .KB_C_FN_DEFINITION_START
* unsigned short AT91F_MII_ReadPhy (AT91PS_EMAC pEmac, unsigned char addr)
* This private function reads the PHY device.
* .KB_C_FN_DEFINITION_END
*/
static unsigned short
AT91F_MII_ReadPhy (AT91PS_EMAC pEmac, unsigned char addr)
{
unsigned value = 0x60020000 | (addr << 18);
pEmac->EMAC_CTL |= AT91C_EMAC_MPE;
pEmac->EMAC_MAN = value;
while(!((pEmac->EMAC_SR) & AT91C_EMAC_IDLE));
pEmac->EMAC_CTL &= ~AT91C_EMAC_MPE;
return (pEmac->EMAC_MAN & 0x0000ffff);
}
/*
* .KB_C_FN_DEFINITION_START
* void MII_GetLinkSpeed(AT91PS_EMAC pEmac)
* This private function determines the link speed set by the PHY.
* .KB_C_FN_DEFINITION_END
*/
static void
MII_GetLinkSpeed(AT91PS_EMAC pEmac)
{
unsigned short stat2;
unsigned update = 0;
stat2 = AT91F_MII_ReadPhy(pEmac, MII_STS2_REG);
if (!(stat2 & 0x400)) {
return ;
} else if (stat2 & 0x4000) {
update |= AT91C_EMAC_SPD;
if (stat2 & 0x200) {
update |= AT91C_EMAC_FD;
}
} else if (stat2 & 0x200) {
update |= AT91C_EMAC_FD;
}
pEmac->EMAC_CFG =
(pEmac->EMAC_CFG & ~(AT91C_EMAC_SPD | AT91C_EMAC_FD)) | update;
}
/*
* .KB_C_FN_DEFINITION_START
* void AT91F_EmacEntry(void)
* This private function initializes the EMAC on the chip.
* .KB_C_FN_DEFINITION_END
*/
static void
AT91F_EmacEntry(void)
{
unsigned i;
char *pRxPacket = (char*)RX_DATA_START;
AT91PS_EMAC pEmac = AT91C_BASE_EMAC;
for (i = 0; i < MAX_RX_PACKETS; ++i) {
p_rxBD[i].address = (unsigned)pRxPacket;
p_rxBD[i].size = 0;
pRxPacket += RX_PACKET_SIZE;
}
// Set the WRAP bit at the end of the list descriptor
p_rxBD[MAX_RX_PACKETS-1].address |= 0x02;
pEmac->EMAC_CTL = 0;
if(!(pEmac->EMAC_SR & AT91C_EMAC_LINK))
MII_GetLinkSpeed(pEmac);
// the sequence write EMAC_SA1L and write EMAC_SA1H must be respected
pEmac->EMAC_SA1L = ((unsigned)localMACAddr[2] << 24) |
((unsigned)localMACAddr[3] << 16) | ((int)localMACAddr[4] << 8) |
localMACAddr[5];
pEmac->EMAC_SA1H = ((unsigned)localMACAddr[0] << 8) | localMACAddr[1];
pEmac->EMAC_RBQP = (unsigned) p_rxBD;
pEmac->EMAC_RSR |= (AT91C_EMAC_OVR | AT91C_EMAC_REC | AT91C_EMAC_BNA);
pEmac->EMAC_CFG |= AT91C_EMAC_CAF;
pEmac->EMAC_CFG = (pEmac->EMAC_CFG & ~(AT91C_EMAC_CLK)) |
AT91C_EMAC_CLK_HCLK_32;
pEmac->EMAC_CTL |= (AT91C_EMAC_TE | AT91C_EMAC_RE);
pEmac->EMAC_TAR = (unsigned)transmitBuffer;
}
/* ************************** GLOBAL FUNCTIONS ********************************/
/*
* .KB_C_FN_DEFINITION_START
* void SetMACAddress(unsigned low_address, unsigned high_address)
* This global function sets the MAC address. low_address is the first
* four bytes while high_address is the last 2 bytes of the 48-bit value.
* .KB_C_FN_DEFINITION_END
*/
void
SetMACAddress(unsigned low_address, unsigned high_address)
{
AT91PS_EMAC pEmac = AT91C_BASE_EMAC;
AT91PS_PMC pPMC = AT91C_BASE_PMC;
/* enable the peripheral clock before using EMAC */
pPMC->PMC_PCER = ((unsigned) 1 << AT91C_ID_EMAC);
pEmac->EMAC_SA1L = low_address;
pEmac->EMAC_SA1H = (high_address & 0x0000ffff);
localMACAddr[0] = (low_address >> 0) & 0xFF;
localMACAddr[1] = (low_address >> 8) & 0xFF;
localMACAddr[2] = (low_address >> 16) & 0xFF;
localMACAddr[3] = (low_address >> 24) & 0xFF;
localMACAddr[4] = (high_address >> 0) & 0xFF;
localMACAddr[5] = (high_address >> 8) & 0xFF;
localMACSet = 1;
// low_address & 0x000000ff = first byte in address
// low_address & 0x0000ff00 = next
// low_address & 0x00ff0000 = next
// low_address & 0xff000000 = next
// high_address & 0x000000ff = next
// high_address & 0x0000ff00 = last byte in address
}
/*
* .KB_C_FN_DEFINITION_START
* void SetServerIPAddress(unsigned address)
* This global function sets the IP of the TFTP download server.
* .KB_C_FN_DEFINITION_END
*/
void
SetServerIPAddress(unsigned address)
{
// force update in case the IP has changed
serverMACSet = 0;
serverIPAddr = address;
}
/*
* .KB_C_FN_DEFINITION_START
* void SetLocalIPAddress(unsigned address)
* This global function sets the IP of this module.
* .KB_C_FN_DEFINITION_END
*/
void
SetLocalIPAddress(unsigned address)
{
// force update in case the IP has changed
serverMACSet = 0;
localIPAddr = address;
}
/*
* .KB_C_FN_DEFINITION_START
* void TFTP_Download(unsigned address, char *filename)
* This global function initiates and processes a tftp download request.
* The server IP, local IP, local MAC must be set before this function is
* executed.
* .KB_C_FN_DEFINITION_END
*/
void
TFTP_Download(unsigned address, char *filename)
{
unsigned thisSeconds, running, state;
int timeout, tickUpdate;
if (!address) {
// report last transfer information
printf("Last tftp transfer info --\r\n"
"address: 0x%x\r\n"
" size: 0x%x\r\n", lastAddress, lastSize);
return ;
}
if ((!localMACSet) || (!localIPAddr) || (!serverIPAddr))
return ;
if (!MAC_init) {
AT91C_BASE_PMC->PMC_PCER = 1u << AT91C_ID_EMAC;
AT91C_BASE_PIOA->PIO_ASR =
AT91C_PA14_ERXER |
AT91C_PA12_ERX0 |
AT91C_PA13_ERX1 |
AT91C_PA8_ETXEN |
AT91C_PA16_EMDIO |
AT91C_PA9_ETX0 |
AT91C_PA10_ETX1 |
AT91C_PA11_ECRS_ECRSDV |
AT91C_PA15_EMDC |
AT91C_PA7_ETXCK_EREFCK;
AT91C_BASE_PIOA->PIO_BSR = 0;
AT91C_BASE_PIOA->PIO_PDR =
AT91C_PA14_ERXER |
AT91C_PA12_ERX0 |
AT91C_PA13_ERX1 |
AT91C_PA8_ETXEN |
AT91C_PA16_EMDIO |
AT91C_PA9_ETX0 |
AT91C_PA10_ETX1 |
AT91C_PA11_ECRS_ECRSDV |
AT91C_PA15_EMDC |
AT91C_PA7_ETXCK_EREFCK;
AT91C_BASE_PIOB->PIO_ASR = 0;
AT91C_BASE_PIOB->PIO_BSR =
AT91C_PB12_ETX2 |
AT91C_PB13_ETX3 |
AT91C_PB14_ETXER |
AT91C_PB15_ERX2 |
AT91C_PB16_ERX3 |
AT91C_PB17_ERXDV |
AT91C_PB18_ECOL |
AT91C_PB19_ERXCK;
AT91C_BASE_PIOB->PIO_PDR =
AT91C_PB12_ETX2 |
AT91C_PB13_ETX3 |
AT91C_PB14_ETXER |
AT91C_PB15_ERX2 |
AT91C_PB16_ERX3 |
AT91C_PB17_ERXDV |
AT91C_PB18_ECOL |
AT91C_PB19_ERXCK;
MAC_init = 1;
}
AT91F_EmacEntry();
GetServerAddress();
lastAddress = address;
dlAddress = (char*)address;
lastSize = 0;
running = 1;
state = TFTP_WAITING_SERVER_MAC;
timeout = 10;
thisSeconds = GetSeconds();
serverPort = SWAP16(69);
localPort++; /* In network byte order, but who cares */
ackBlock = -1;
while (running && timeout) {
CheckForNewPacket();
tickUpdate = 0;
if (thisSeconds != GetSeconds()) {
tickUpdate = 1;
--timeout;
thisSeconds = GetSeconds();
}
switch (state) {
case TFTP_WAITING_SERVER_MAC:
if (serverMACSet) {
state = TFTP_SEND_REQUEST;
break;
}
if (tickUpdate)
GetServerAddress();
break;
case TFTP_SEND_REQUEST:
// send request for file
if (ackBlock != -1) {
state = TFTP_GET_DATA;
break;
}
if (tickUpdate)
TFTP_RequestFile(filename);
break;
case TFTP_GET_DATA:
// receiving data
if (ackBlock == -2) {
state = TFTP_COMPLETE;
break;
}
break;
case TFTP_COMPLETE:
default:
running = 0;
break;
}
}
}
/*
* .KB_C_FN_DEFINITION_START
* void EMAC_Init(void)
* This global function initializes variables used in tftp transfers.
* .KB_C_FN_DEFINITION_END
*/
void
EMAC_Init(void)
{
p_rxBD = (receive_descriptor_t*)RX_BUFFER_START;
localMACSet = 0;
serverMACSet = 0;
localPort = SWAP16(0x8002);
lastAddress = 0;
lastSize = 0;
MAC_init = 0;
}