genode/repos/os/src/drivers/nic/lan9118/lan9118.h

421 lines
10 KiB
C++

/*
* \brief LAN9118 NIC driver
* \author Norman Feske
* \author Stefan Kalkowski
* \date 2011-05-21
*/
/*
* Copyright (C) 2011-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _DRIVERS__NIC__SPEC__LAN9118__LAN9118_H_
#define _DRIVERS__NIC__SPEC__LAN9118__LAN9118_H_
#include <base/attached_dataspace.h>
#include <base/log.h>
#include <util/misc_math.h>
#include <irq_session/client.h>
#include <timer_session/connection.h>
#include <nic/component.h>
class Lan9118 : public Nic::Session_component
{
private:
/*
* Noncopyable
*/
Lan9118(Lan9118 const &);
Lan9118 &operator = (Lan9118 const &);
/**
* MMIO register offsets
*/
enum Register {
RX_DATA_FIFO = 0x00,
TX_DATA_FIFO = 0x20,
RX_STATUS_FIFO = 0x40,
RX_STATUS_FIFO_PEEK = 0x44,
TX_STATUS_FIFO = 0x48,
TX_STATUS_FIFO_PEEK = 0x4c,
ID_REV = 0x50,
IRQ_CFG = 0x54,
INT_STS = 0x58,
INT_EN = 0x5c,
BYTE_TEST = 0x64,
TX_CFG = 0x70,
HW_CFG = 0x74,
RX_FIFO_INF = 0x7c,
TX_FIFO_INF = 0x80,
MAC_CSR_CMD = 0xa4,
MAC_CSR_DATA = 0xa8,
};
/**
* MAC registers, indirectly accessed via 'MAC_CSR_CMD' and
* 'MAC_CSR_DATA'
*/
enum Mac_register {
MAC_CR = 1,
MAC_ADDRH = 2,
MAC_ADDRL = 3,
};
/**
* Bits in MAC_CSR_CMD register
*/
enum {
MAC_CSR_CMD_BUSY = (1 << 31),
MAC_CSR_CMD_READ = (1 << 30),
MAC_CSR_CMD_WRITE = (0 << 30),
};
Genode::Attached_dataspace _mmio;
volatile Genode::uint32_t *_reg_base;
Timer::Connection _timer;
Nic::Mac_address _mac_addr { };
Genode::Irq_session_client _irq;
Genode::Signal_handler<Lan9118> _irq_handler;
/**
* Information about a received packet, used internally
*/
struct Rx_packet_info
{
Genode::size_t size;
Rx_packet_info(Genode::uint32_t status)
: size((status & 0x3fff0000) >> 16)
{ }
};
/**
* Read 32-bit wide MMIO register
*/
Genode::uint32_t _reg_read(Register reg)
{
return _reg_base[reg >> 2];
}
void _reg_write(Register reg, Genode::uint32_t value)
{
_reg_base[reg >> 2] = value;
}
/**
* Return true of MAC CSR is busy
*/
bool _mac_csr_busy()
{
return _reg_read(MAC_CSR_CMD) & MAC_CSR_CMD_BUSY;
}
/**
* Wait for the completion of a MAC CSR operation
*/
void _mac_csr_wait_ready()
{
for (unsigned i = 0; i < 10; i++) {
if (!_mac_csr_busy())
return;
_timer.msleep(10);
}
Genode::error("timeout while waiting for completeness of MAC CSR access");
}
/**
* Read MAC control / status register
*
* The MAC CSRs are accessed indirectly via 'MAC_CSR_CMD' and
* 'MAC_CSR_DATA'.
*/
Genode::uint32_t _mac_csr_read(Mac_register reg)
{
_reg_write(MAC_CSR_CMD, reg | MAC_CSR_CMD_READ | MAC_CSR_CMD_BUSY);
_mac_csr_wait_ready();
return _reg_read(MAC_CSR_DATA);
}
/**
* Write MAC control / status register
*/
void _mac_csr_write(Mac_register reg, Genode::uint32_t value)
{
_reg_write(MAC_CSR_DATA, value);
_reg_write(MAC_CSR_CMD, reg | MAC_CSR_CMD_WRITE | MAC_CSR_CMD_BUSY);
_mac_csr_wait_ready();
}
/**
* Reset device
*
* \return true on success
*/
bool _soft_reset()
{
enum { HW_CFG_SRST = (1 << 0) };
_reg_write(HW_CFG, HW_CFG_SRST);
for (unsigned i = 0; i < 10; i++) {
_timer.msleep(10);
if (!(_reg_read(HW_CFG) & HW_CFG_SRST))
return true;
}
return false;
}
/**
* Return true if the NIC has least one incoming packet pending
*/
bool _rx_packet_avail()
{
return _reg_read(RX_FIFO_INF) & 0xff0000;
}
/**
* Acknowledge packet at the NIC
*/
Rx_packet_info _rx_packet_info()
{
return Rx_packet_info(_reg_read(RX_STATUS_FIFO));
}
/**
* Return number of bytes currently available in rx data fifo
*/
Genode::size_t _rx_data_pending()
{
return _reg_read(RX_FIFO_INF) & 0xffff;
}
protected:
bool _send()
{
if (!_tx.sink()->ready_to_ack())
return false;
if (!_tx.sink()->packet_avail())
return false;
Genode::Packet_descriptor packet = _tx.sink()->get_packet();
if (!packet.size() || !_tx.sink()->packet_valid(packet)) {
Genode::warning("Invalid tx packet");
return true;
}
/* limit size to 11 bits, the maximum supported by lan9118 */
enum { MAX_PACKET_SIZE_LOG2 = 11 };
Genode::size_t const max_size = (1 << MAX_PACKET_SIZE_LOG2) - 1;
if (packet.size() > max_size) {
Genode::error("packet size ", packet.size(), " too large, "
"limit is ", max_size);
return true;
}
enum { FIRST_SEG = (1 << 13),
LAST_SEG = (1 << 12) };
Genode::uint32_t const cmd_a = packet.size() | FIRST_SEG | LAST_SEG,
cmd_b = packet.size();
unsigned count = Genode::align_addr(packet.size(), 2) >> 2;
Genode::uint32_t *src = (Genode::uint32_t *)_tx.sink()->packet_content(packet);
/* check space left in tx data fifo */
Genode::size_t const fifo_avail = _reg_read(TX_FIFO_INF) & 0xffff;
if (fifo_avail < count*4 + sizeof(cmd_a) + sizeof(cmd_b)) {
Genode::error("tx fifo overrun, ignore packet");
_tx.sink()->acknowledge_packet(packet);
return false;
}
_reg_write(TX_DATA_FIFO, cmd_a);
_reg_write(TX_DATA_FIFO, cmd_b);
/* supply payload to transmit fifo */
for (; count--; src++)
_reg_write(TX_DATA_FIFO, *src);
_tx.sink()->acknowledge_packet(packet);
return true;
}
void _handle_packet_stream() override
{
while (_rx.source()->ack_avail())
_rx.source()->release_packet(_rx.source()->get_acked_packet());
while (_send()) ;
}
void _handle_irq()
{
using namespace Genode;
_handle_packet_stream();
while (_rx_packet_avail() && _rx.source()->ready_to_submit()) {
/* read packet from NIC, copy to client buffer */
Rx_packet_info packet = _rx_packet_info();
/* align size to 32-bit boundary */
size_t const size = align_addr(packet.size, 2);
/* allocate rx packet buffer */
Nic::Packet_descriptor p;
try {
p = _rx.source()->alloc_packet(size);
} catch (Session::Rx::Source::Packet_alloc_failed) { return; }
uint32_t *dst = (uint32_t *)_rx.source()->packet_content(p);
/* calculate number of words to be read from rx fifo */
size_t count = min(size, _rx_data_pending()) >> 2;
/* copy payload from rx fifo to client buffer */
for (; count--; )
*dst++ = _reg_read(RX_DATA_FIFO);
_rx.source()->submit_packet(p);
}
/* acknowledge all pending irqs */
_reg_write(INT_STS, ~0);
_irq.ack_irq();
}
public:
/**
* Exception type
*/
class Device_not_supported { };
/**
* Constructor
*
* \throw Device_not_supported
*/
Lan9118(Genode::Io_mem_dataspace_capability ds_cap,
Genode::Irq_session_capability irq_cap,
Genode::size_t const tx_buf_size,
Genode::size_t const rx_buf_size,
Genode::Allocator & rx_block_md_alloc,
Genode::Env & env)
: Session_component(tx_buf_size, rx_buf_size, Genode::CACHED,
rx_block_md_alloc, env),
_mmio(env.rm(), ds_cap),
_reg_base(_mmio.local_addr<Genode::uint32_t>()),
_timer(env),
_irq(irq_cap),
_irq_handler(env.ep(), *this, &Lan9118::_handle_irq)
{
_irq.sigh(_irq_handler);
_irq.ack_irq();
unsigned long const id_rev = _reg_read(ID_REV),
byte_order = _reg_read(BYTE_TEST);
using namespace Genode;
log("id/rev: ", Hex(id_rev));
log("byte order: ", Hex(byte_order));
enum { EXPECTED_BYTE_ORDER = 0x87654321 };
if (byte_order != EXPECTED_BYTE_ORDER) {
error("invalid byte order, expected ", Hex(EXPECTED_BYTE_ORDER));
throw Device_not_supported();
}
enum { EXPECTED_ID = 0x01180000 };
if ((id_rev & 0xffff0000) != EXPECTED_ID) {
error("device ID not supported, expected ", Hex(EXPECTED_ID));
throw Device_not_supported();
}
if (!_soft_reset()) {
error("soft reset timed out");
throw Device_not_supported();
}
/* print MAC address */
unsigned mac_addr_lo = _mac_csr_read(MAC_ADDRL),
mac_addr_hi = _mac_csr_read(MAC_ADDRH);
_mac_addr.addr[5] = (mac_addr_hi >> 8) & 0xff;
_mac_addr.addr[4] = (mac_addr_hi >> 0) & 0xff;
_mac_addr.addr[3] = (mac_addr_lo >> 24) & 0xff;
_mac_addr.addr[2] = (mac_addr_lo >> 16) & 0xff;
_mac_addr.addr[1] = (mac_addr_lo >> 8) & 0xff;
_mac_addr.addr[0] = (mac_addr_lo >> 0) & 0xff;
log("MAC address: ", _mac_addr);
/* configure MAC */
enum { MAC_CR_TXEN = (1 << 3),
MAC_CR_RXEN = (1 << 2) };
_mac_csr_write(MAC_CR, MAC_CR_TXEN | MAC_CR_RXEN);
enum { TX_CFG_TX_ON = (1 << 1),
TX_CFG_TXSAO = (1 << 2), };
/* enable transmitter, let nic ignore the tx status fifo */
_reg_write(TX_CFG, TX_CFG_TX_ON | TX_CFG_TXSAO);
/* reset interrupt state */
_reg_write(INT_EN, 0); /* disable */
_reg_write(INT_STS, ~0); /* acknowledge all pending irqs */
/* enable interrupts for reception */
enum { INT_EN_RSFL = (1 << 3),
INT_EN_RXSTOP = (1 << 24),
INT_EN_SW = (1 << 31) };
_reg_write(INT_EN, INT_EN_SW | INT_EN_RSFL | INT_EN_RXSTOP);
/* enable interrupts at 'IRQ_CFG' */
enum { IRQ_CFG_EN = (1 << 8),
IRQ_CFG_POL = (1 << 4), /* active high irq polarity */
IRQ_CFG_TYPE = (1 << 0) /* not open drain */ };
_reg_write(IRQ_CFG, IRQ_CFG_EN | IRQ_CFG_POL | IRQ_CFG_TYPE);
}
/**
* Destructor
*/
~Lan9118()
{
Genode::log("disable NIC");
/* disable transmitter */
_reg_write(TX_CFG, 0);
/* disable rx and tx at MAX */
_mac_csr_write(MAC_CR, 0);
}
/**************************************
** Nic::Session_component interface **
**************************************/
Nic::Mac_address mac_address() override
{
return _mac_addr;
}
bool link_state() override
{
/* XXX always return true for now */
return true;
}
};
#endif /* _DRIVERS__NIC__SPEC__LAN9118__LAN9118_H_ */