This commit is joint work by Meisaka Yukara <Meisaka.Yukara@gmail.com> and Jonas 'Sortie' Termansen <sortie@maxsi.org>.
1062 lines
31 KiB
C++
1062 lines
31 KiB
C++
/*
|
|
* Copyright (c) 2015, 2016, 2017 Jonas 'Sortie' Termansen.
|
|
* Copyright (c) 2015 Meisaka Yukara.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
* net/em/em.cpp
|
|
* 825xx driver.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <sortix/stat.h>
|
|
|
|
#include <sortix/kernel/addralloc.h>
|
|
#include <sortix/kernel/descriptor.h>
|
|
#include <sortix/kernel/inode.h>
|
|
#include <sortix/kernel/interrupt.h>
|
|
#include <sortix/kernel/ioctx.h>
|
|
#include <sortix/kernel/log.h>
|
|
#include <sortix/kernel/memorymanagement.h>
|
|
#include <sortix/kernel/if.h>
|
|
#include <sortix/kernel/packet.h>
|
|
#include <sortix/kernel/panic.h>
|
|
#include <sortix/kernel/pci.h>
|
|
#include <sortix/kernel/pci-mmio.h>
|
|
#include <sortix/kernel/refcount.h>
|
|
|
|
#include "../arp.h"
|
|
#include "../ether.h"
|
|
|
|
#include "em.h"
|
|
#include "emregs.h"
|
|
|
|
namespace Sortix {
|
|
namespace EM {
|
|
|
|
// TODO: Switch this to a bitmap.
|
|
struct EM_Features
|
|
{
|
|
bool eeprom; // EEPROM access present
|
|
bool serdes; // SerDes/TBI supported
|
|
bool pcie; // PCIe Device
|
|
};
|
|
|
|
enum EM_FEATURE_INDEX
|
|
{
|
|
emdefault,
|
|
em8254xS,
|
|
em8254xM,
|
|
em8256xM,
|
|
em8257xM,
|
|
em8257xS,
|
|
em82576S,
|
|
em8258xM,
|
|
em8258xS,
|
|
emfeaturemax // this must be last
|
|
};
|
|
|
|
static const struct EM_Features em_feature[emfeaturemax] =
|
|
{
|
|
[emdefault] = { false, false, false },
|
|
[em8254xS] = { true, true, false },
|
|
[em8254xM] = { true, false, false },
|
|
[em8256xM] = { false, false, true },
|
|
[em8257xM] = { false, false, true },
|
|
[em8257xS] = { false, true, true },
|
|
[em82576S] = { false, true, true },
|
|
[em8258xM] = { false, false, true },
|
|
[em8258xS] = { false, true, true },
|
|
};
|
|
|
|
struct short_filter
|
|
{
|
|
uint16_t feature_index;
|
|
uint16_t device_id;
|
|
};
|
|
|
|
// TODO: Check against page 93 (physical 108) in
|
|
// pci-pci-x-family-gbe-controllers-software-dev-manual.pdf
|
|
static const struct short_filter em_table[] = {
|
|
{ em8254xS, PCI_PRODUCT_INTEL_DH89XXCC_SGMII },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_DH89XXCC_S },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_DH89XXCC_BPLANE },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_DH89XXCC_SFP },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82542 },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82543GC_F },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82543GC_C },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82544EI_C },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82544EI_F },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82544GC_C },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82544GC_LOM },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82540EM_D },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82545EM_C },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82546EB_C },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82545EM_F },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82546EB_F },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82541EI_C },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82541ER_LOM },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82540EM_M },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82540EP_M },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82540EP_D },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82541EI_M },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82547EI },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82547EI_M },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82546EB_CQ },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82540EP_LP },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82545GM_C },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82545GM_F },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82545GM_S },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH8_IGP_M_AMT },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH8_IGP_AMT },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH8_IGP_C },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH8_IFE },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH8_IGP_M },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82571EB_C },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82571EB_F },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82571EB_S },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82547GI },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82541GI_C },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82541GI_M },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82541ER_C },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82546GB_C },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82546GB_F },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82546GB_S },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_82541GI_LF },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82572EI_C },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82572EI_F },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82572EI_S },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82546GB_PCIE },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82573E },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82573E_IAMT },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82573E_IDE },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82573E_KCS },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82573E_SERIAL },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_80003ES2LAN_CD },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_80003ES2LAN_SD },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82546GB_CQ },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82573L },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82546GB_2 },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82571EB_AT },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82571EB_AF },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82571EB_CQ },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82571EB_FQ },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82575EB_C },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82575EB_S },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82573L_PL_1 },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82573V_PM },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82573E_PM },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82573L_PL_2 },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82546GB_CQ_K },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82572EI },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_80003ES2LAN_C },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_80003ES2LAN_S },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82571EB_CQ_LP },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH9_IGP_AMT },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH9_IGP_M },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH9_IFE },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH9_IFE_G },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH9_IFE_GT },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH8_IFE_GT },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH8_IFE_G },
|
|
{ em82576S, PCI_PRODUCT_INTEL_82576 },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH9_IGP_M_V },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH10_R_BM_LM },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH10_R_BM_LF },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH10_R_BM_V },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82574L },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82571PT_CQ },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82575GB_CQ },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82571EB_SD },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82571EB_SQ },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH10_D_BM_LM },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH10_D_BM_LF },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82575GB_QP_PM },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH9_BM },
|
|
{ em82576S, PCI_PRODUCT_INTEL_82576_F },
|
|
{ em82576S, PCI_PRODUCT_INTEL_82576_S },
|
|
{ em82576S, PCI_PRODUCT_INTEL_82576_CQ },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82577LM },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82577LC },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82578DM },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_82578DC },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH9_IGP_M_AMT },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82574LA },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82544EI_A4 },
|
|
{ em8254xS, PCI_PRODUCT_INTEL_82544GC_A4 },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH8_82567V_3 },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82579LM },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_82579V },
|
|
{ em82576S, PCI_PRODUCT_INTEL_82576_NS },
|
|
{ em8258xS, PCI_PRODUCT_INTEL_82583V },
|
|
{ em82576S, PCI_PRODUCT_INTEL_82576_SQ },
|
|
{ em8258xS, PCI_PRODUCT_INTEL_82580_C },
|
|
{ em8258xS, PCI_PRODUCT_INTEL_82580_F },
|
|
{ em8258xS, PCI_PRODUCT_INTEL_82580_S },
|
|
{ em8258xS, PCI_PRODUCT_INTEL_82580_SGMII },
|
|
{ em8258xS, PCI_PRODUCT_INTEL_82580_CD },
|
|
{ em82576S, PCI_PRODUCT_INTEL_82576_NS_S },
|
|
{ em82576S, PCI_PRODUCT_INTEL_I350_C },
|
|
{ em82576S, PCI_PRODUCT_INTEL_I350_F },
|
|
{ em82576S, PCI_PRODUCT_INTEL_I350_S },
|
|
{ em82576S, PCI_PRODUCT_INTEL_I350_SGMII },
|
|
{ em82576S, PCI_PRODUCT_INTEL_82576_CQ_ET2 },
|
|
{ em8258xS, PCI_PRODUCT_INTEL_82580_FQ },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_I210_C },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_I210_F },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_I210_S },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_I210_SGMII },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_I211_C },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_I217_LM },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_I217_V },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_I218_V },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_I218_LM },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_I210_C_NF },
|
|
{ em8257xS, PCI_PRODUCT_INTEL_I210_S_NF },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_I218_LM_2 },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_I218_V_2 },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_I218_LM_3 },
|
|
{ em8257xM, PCI_PRODUCT_INTEL_I218_V_3 },
|
|
{ em8256xM, PCI_PRODUCT_INTEL_ICH9_IGP_C },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_EP80579_LAN_1 },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_EP80579_LAN_4 },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_EP80579_LAN_2 },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_EP80579_LAN_5 },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_EP80579_LAN_3 },
|
|
{ em8254xM, PCI_PRODUCT_INTEL_EP80579_LAN_6 }
|
|
};
|
|
|
|
static const size_t filter_count = sizeof(em_table) / sizeof(em_table[0]);
|
|
|
|
static const uint32_t understood_interrupts =
|
|
EM_INTERRUPT_TXDW | EM_INTERRUPT_TXQE |
|
|
EM_INTERRUPT_LSC | EM_INTERRUPT_RXDMT0 | EM_INTERRUPT_RXO |
|
|
EM_INTERRUPT_RXT0 | EM_INTERRUPT_MDAC | EM_INTERRUPT_RXCFG |
|
|
EM_INTERRUPT_TXD_LOW | EM_INTERRUPT_SRPD;
|
|
|
|
// TODO: Convert endian when accessing these.
|
|
struct rx_desc
|
|
{
|
|
uint64_t address;
|
|
uint16_t length;
|
|
uint16_t checksum;
|
|
uint8_t status;
|
|
uint8_t errors;
|
|
uint16_t special;
|
|
};
|
|
|
|
// TODO: Convert endian when accessing these.
|
|
struct tx_desc_tcpdata
|
|
{
|
|
uint64_t address;
|
|
uint32_t lencmd;
|
|
uint8_t status;
|
|
uint8_t opts;
|
|
uint16_t special;
|
|
};
|
|
|
|
class EM : public NetworkInterface
|
|
{
|
|
public:
|
|
EM(uint32_t devaddr);
|
|
virtual ~EM();
|
|
|
|
public:
|
|
bool Send(Ref<Packet> pkt);
|
|
void Reset();
|
|
__attribute__((format(printf, 2, 3)))
|
|
void Log(const char* format, ...);
|
|
uint32_t Read32(uint32_t reg);
|
|
void Write32(uint32_t reg, uint32_t value);
|
|
uint16_t ReadEEPROM(uint16_t reg);
|
|
uint16_t ReadPHY(uint8_t reg);
|
|
void WritePHY(uint8_t reg, uint16_t value);
|
|
bool WaitLinkResolved();
|
|
void RegisterInterrupts();
|
|
bool AddReceiveDescriptor(Ref<Packet> pkt);
|
|
bool AddTransmitDescriptor(Ref<Packet> pkt);
|
|
bool CanAddTransmit();
|
|
|
|
private:
|
|
static void InterruptHandler(struct interrupt_context*, void*);
|
|
static void InterruptWorkHandler(void* context);
|
|
void OnInterrupt();
|
|
void InterruptWork();
|
|
|
|
public:
|
|
uint32_t devaddr;
|
|
struct interrupt_handler interrupt_registration;
|
|
struct interrupt_work interrupt_work;
|
|
uint32_t interrupt_work_icr;
|
|
uint8_t interrupt;
|
|
addralloc_t mmio_alloc;
|
|
volatile uint8_t* mmio_base;
|
|
paddrmapped_t rdesc_alloc;
|
|
paddrmapped_t tdesc_alloc;
|
|
const struct EM_Features* feature;
|
|
|
|
private:
|
|
struct rx_desc* rdesc;
|
|
struct tx_desc_tcpdata* tdesc;
|
|
Ref<Packet>* rpackets;
|
|
Ref<Packet>* tpackets;
|
|
Ref<Packet> tx_queue_first;
|
|
Ref<Packet> tx_queue_last;
|
|
kthread_mutex_t tx_lock;
|
|
kthread_mutex_t eeprom_lock;
|
|
kthread_mutex_t phy_lock;
|
|
uint32_t rx_count;
|
|
uint32_t tx_count;
|
|
uint32_t rx_tail;
|
|
uint32_t rx_prochead;
|
|
uint32_t tx_tail;
|
|
uint32_t tx_prochead;
|
|
uint32_t intcount;
|
|
|
|
};
|
|
|
|
// TODO: Printing atomicy!
|
|
void EM::Log(const char* format, ...)
|
|
{
|
|
Log::PrintF("[%s] ", ifinfo.name);
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
Log::PrintFV(format, ap);
|
|
va_end(ap);
|
|
Log::PrintF("\n");
|
|
}
|
|
|
|
EM::EM(uint32_t devaddr)
|
|
{
|
|
ifinfo.type = IF_TYPE_ETHERNET;
|
|
ifinfo.features = IF_FEATURE_ETHERNET_CRC_OFFLOAD;
|
|
ifinfo.addrlen = ETHER_ADDR_LEN;
|
|
ifstatus.mtu = ETHERMTU;
|
|
this->devaddr = devaddr;
|
|
interrupt = 0;
|
|
memset(&interrupt_registration, 0, sizeof(interrupt_registration));
|
|
interrupt_work.handler = InterruptWorkHandler;
|
|
interrupt_work.context = this;
|
|
interrupt_work_icr = 0;
|
|
memset(&mmio_alloc, 0, sizeof(mmio_alloc));
|
|
mmio_base = NULL;
|
|
memset(&rdesc_alloc, 0, sizeof(rdesc_alloc));
|
|
memset(&tdesc_alloc, 0, sizeof(tdesc_alloc));
|
|
feature = NULL;
|
|
rdesc = NULL;
|
|
tdesc = NULL;
|
|
rpackets = NULL;
|
|
tpackets = NULL;
|
|
// tx_queue_first has constructor.
|
|
// tx_queue_last has constructor.
|
|
tx_lock = KTHREAD_MUTEX_INITIALIZER;
|
|
eeprom_lock = KTHREAD_MUTEX_INITIALIZER;
|
|
phy_lock = KTHREAD_MUTEX_INITIALIZER;
|
|
rx_count = 0;
|
|
tx_count = 0;
|
|
rx_tail = 0;
|
|
rx_prochead = 0;
|
|
tx_tail = 0;
|
|
tx_prochead = 0;
|
|
intcount = 0;
|
|
}
|
|
|
|
EM::~EM()
|
|
{
|
|
if ( interrupt ) // TODO: Use an interrupt registed bool instead?
|
|
Interrupt::UnregisterHandler(interrupt, &interrupt_registration);
|
|
// TODO: Other stuff to clean up. How is this destructor even reachable
|
|
// under the driver model? What is it supposed to clean up?
|
|
}
|
|
|
|
uint32_t EM::Read32(uint32_t reg)
|
|
{
|
|
return *((volatile little_uint32_t*) (mmio_base + reg));
|
|
}
|
|
|
|
void EM::Write32(uint32_t reg, uint32_t value)
|
|
{
|
|
*((volatile little_uint32_t*) (mmio_base + reg)) = value;
|
|
}
|
|
|
|
uint16_t EM::ReadEEPROM(uint16_t reg)
|
|
{
|
|
ScopedLock lock(&eeprom_lock);
|
|
|
|
uint32_t value = ((uint32_t) reg) << EM_MAIN_REG_EERD_ADDR_SHIFT |
|
|
EM_MAIN_REG_EERD_START;
|
|
Write32(EM_MAIN_REG_EERD, value);
|
|
// TODO: Use a fancy poll with timeout.
|
|
while ( !((value = Read32(EM_MAIN_REG_EERD)) & EM_MAIN_REG_EERD_DONE) )
|
|
{
|
|
}
|
|
return (value & EM_MAIN_REG_EERD_DATA_MASK) >> EM_MAIN_REG_EERD_DATA_SHIFT;
|
|
}
|
|
|
|
uint16_t EM::ReadPHY(uint8_t reg)
|
|
{
|
|
ScopedLock lock(&phy_lock);
|
|
|
|
uint32_t mdic = Read32(EM_MAIN_REG_MDIC);
|
|
if ( mdic & EM_MAIN_REG_MDIC_E )
|
|
{
|
|
// TODO: Error.
|
|
}
|
|
mdic = ((uint32_t) reg) << EM_MAIN_REG_MDIC_REGADD_SHIFT |
|
|
EM_MAIN_REG_MDIC_PHYADD_PHY_ONE_AND_ONLY |
|
|
EM_MAIN_REG_MDIC_OP_READ;
|
|
Write32(EM_MAIN_REG_MDIC, mdic);
|
|
uint32_t stop_bits = EM_MAIN_REG_MDIC_R | EM_MAIN_REG_MDIC_E;
|
|
// TODO: Use a fancy poll with timeout.
|
|
while ( !((mdic = Read32(EM_MAIN_REG_MDIC)) & stop_bits) )
|
|
{
|
|
}
|
|
if ( mdic & EM_MAIN_REG_MDIC_E )
|
|
{
|
|
// TODO: Error.
|
|
}
|
|
return (mdic & EM_MAIN_REG_MDIC_DATA_MASK) >> EM_MAIN_REG_MDIC_DATA_SHIFT;
|
|
}
|
|
|
|
void EM::WritePHY(uint8_t reg, uint16_t value)
|
|
{
|
|
ScopedLock lock(&phy_lock);
|
|
|
|
uint32_t mdic = Read32(EM_MAIN_REG_MDIC);
|
|
if ( mdic & EM_MAIN_REG_MDIC_E )
|
|
{
|
|
// TODO: Error.
|
|
}
|
|
mdic = ((uint32_t) value) << EM_MAIN_REG_MDIC_DATA_SHIFT |
|
|
((uint32_t) reg) << EM_MAIN_REG_MDIC_REGADD_SHIFT |
|
|
EM_MAIN_REG_MDIC_PHYADD_PHY_ONE_AND_ONLY |
|
|
EM_MAIN_REG_MDIC_OP_WRITE;
|
|
Write32(EM_MAIN_REG_MDIC, mdic);
|
|
uint32_t stop_bits = EM_MAIN_REG_MDIC_R | EM_MAIN_REG_MDIC_E;
|
|
// TODO: Use a fancy poll with timeout.
|
|
while ( !((mdic = Read32(EM_MAIN_REG_MDIC)) & stop_bits) )
|
|
{
|
|
}
|
|
if ( mdic & EM_MAIN_REG_MDIC_E )
|
|
{
|
|
// TODO: Error.
|
|
}
|
|
}
|
|
|
|
bool EM::WaitLinkResolved()
|
|
{
|
|
uint16_t pstatus;
|
|
uint16_t psstat;
|
|
while ( true )
|
|
{
|
|
pstatus = ReadPHY(EM_PHY_REG_PSTATUS);
|
|
psstat = ReadPHY(EM_PHY_REG_PSSTAT);
|
|
if ( !(psstat & EM_PHY_REG_PSSTAT_LINK) )
|
|
return false;
|
|
if ( !(pstatus & EM_PHY_REG_PSTATUS_AN_COMPLETE) )
|
|
{
|
|
// TODO: Is a timeout needed here?
|
|
continue;
|
|
}
|
|
// TODO: Needed?
|
|
if ( !(psstat & EM_PHY_REG_PSSTAT_SPEED_DUPLEX_RESOLVED) )
|
|
continue;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool EM::AddReceiveDescriptor(Ref<Packet> pkt)
|
|
{
|
|
// TODO: Some lock must be owned here?
|
|
uint32_t nextdesc = rx_tail + 1;
|
|
if ( rx_count <= nextdesc )
|
|
nextdesc = 0;
|
|
if ( nextdesc == rx_prochead )
|
|
return false;
|
|
struct rx_desc* desc = &rdesc[rx_tail];
|
|
memset(desc, 0, sizeof(*desc));
|
|
desc->status = 0;
|
|
desc->address = pkt->pmap.phys;
|
|
// TODO: Where are the rest of the fields set? By the hardware?
|
|
rpackets[rx_tail] = pkt;
|
|
rx_tail = nextdesc;
|
|
// TODO: Research whether this is needed, or whether the paging bits do
|
|
// the right thing. Do those bits work on all systems?
|
|
//asm volatile ("wbinvd");
|
|
Write32(EM_MAIN_REG_RDT, rx_tail);
|
|
return true;
|
|
}
|
|
|
|
bool EM::AddTransmitDescriptor(Ref<Packet> pkt) // tx_lock must be locked.
|
|
{
|
|
uint32_t nextdesc = tx_tail + 1;
|
|
if ( tx_count <= nextdesc )
|
|
nextdesc = 0;
|
|
if ( nextdesc == tx_prochead )
|
|
return false;
|
|
struct tx_desc_tcpdata* desc = &tdesc[tx_tail];
|
|
desc->address = pkt->pmap.phys;
|
|
desc->lencmd = EM_TDESC_LENGTH(pkt->length) | EM_TDESC_TYPE_TCPDATA |
|
|
EM_TDESC_CMD_RS | EM_TDESC_CMD_EOP | EM_TDESC_CMD_IFCS;
|
|
desc->status = 0;
|
|
desc->opts = 0;
|
|
desc->special = 0;
|
|
tpackets[tx_tail] = pkt;
|
|
tx_tail = nextdesc;
|
|
// TODO: Research whether this is needed, or whether the paging bits do
|
|
// the right thing. Do those bits work on all systems?
|
|
//asm volatile ("wbinvd");
|
|
Write32(EM_MAIN_REG_TDT, tx_tail);
|
|
return true;
|
|
}
|
|
|
|
bool EM::CanAddTransmit() // tx_lock must be locked.
|
|
{
|
|
uint32_t nextdesc = tx_tail + 1;
|
|
if ( tx_count <= nextdesc )
|
|
nextdesc = 0;
|
|
if ( nextdesc == tx_prochead )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// TODO: This can't fail, change return type to void?
|
|
bool EM::Send(Ref<Packet> pkt)
|
|
{
|
|
ScopedLock lock(&tx_lock);
|
|
if ( !tx_queue_first &&
|
|
CanAddTransmit() &&
|
|
AddTransmitDescriptor(pkt) )
|
|
return true;
|
|
if ( tx_queue_last )
|
|
{
|
|
tx_queue_last->next = pkt;
|
|
tx_queue_last = pkt;
|
|
}
|
|
else
|
|
{
|
|
tx_queue_first = pkt;
|
|
tx_queue_last = pkt;
|
|
}
|
|
pkt->next.Reset();
|
|
return true;
|
|
}
|
|
|
|
void EM::InterruptHandler(struct interrupt_context*, void* user)
|
|
{
|
|
((EM*) user)->OnInterrupt();
|
|
}
|
|
|
|
void EM::RegisterInterrupts()
|
|
{
|
|
interrupt = PCI::GetInterruptIndex(devaddr);
|
|
interrupt_registration.handler = EM::InterruptHandler;
|
|
interrupt_registration.context = this;
|
|
Interrupt::RegisterHandler(interrupt, &interrupt_registration);
|
|
}
|
|
|
|
void EM::OnInterrupt()
|
|
{
|
|
// TODO: Use "Interrupt Acknowledge Auto Mask Register - IAM (000E0h)" to
|
|
// automatically mask set interrupts.
|
|
#if 1
|
|
// TODO: Hack to read ICR correctly (buggy VM or compiler).
|
|
// Yes. I verified the driver works with this hack and doesn't
|
|
// without. Figure out what happened.
|
|
uint32_t icr = *((volatile uint32_t*) (mmio_base + EM_MAIN_REG_ICR));
|
|
#else
|
|
uint32_t icr = Read32(EM_MAIN_REG_ICR);
|
|
#endif
|
|
if ( !icr )
|
|
return;
|
|
intcount++;
|
|
if ( interrupt_work_icr != 0 )
|
|
{
|
|
#if 0
|
|
// TODO: This assertion fired! Is it fine to just ignore the interrupt?
|
|
// Does reading ICR have side effects? Should we read it at the
|
|
// of the interrupt work handler and reschedule the interrupt
|
|
// worker?
|
|
if ( interrupt_work_icr )
|
|
PanicF("em: icr=%#x but interrupt_work_icr=%#x\n", icr, interrupt_work_icr);
|
|
assert(interrupt_work_icr == 0);
|
|
#endif
|
|
return;
|
|
}
|
|
interrupt_work_icr = icr;
|
|
// Mask further interrupts while processing the interrupt.
|
|
Write32(EM_MAIN_REG_IMC, understood_interrupts);
|
|
Interrupt::ScheduleWork(&interrupt_work);
|
|
}
|
|
|
|
void EM::InterruptWorkHandler(void* context)
|
|
{
|
|
((EM*) context)->InterruptWork();
|
|
}
|
|
|
|
void EM::InterruptWork()
|
|
{
|
|
uint32_t icr = interrupt_work_icr;
|
|
uint32_t unhandled = icr & 0x1FFFF;
|
|
if ( icr & EM_INTERRUPT_LSC )
|
|
{
|
|
// TODO: This can block the kernel worker thread that's meant for
|
|
// small non-blocking tasks.
|
|
WaitLinkResolved();
|
|
uint32_t status = Read32(EM_MAIN_REG_STATUS);
|
|
ScopedLock lock(&cfg_lock);
|
|
if ( status & EM_MAIN_REG_STATUS_LU )
|
|
ifstatus.flags |= IF_STATUS_FLAGS_UP;
|
|
else
|
|
ifstatus.flags &= ~IF_STATUS_FLAGS_UP;
|
|
kthread_cond_broadcast(&cfg_cond);
|
|
//if ( status & EM_MAIN_REG_STATUS_LU )
|
|
// Log("Link status change up %s-duplex.",
|
|
// status & EM_MAIN_REG_STATUS_FD ? "full" : "half");
|
|
//else
|
|
// Log("Link status change down.");
|
|
unhandled &= ~EM_INTERRUPT_LSC;
|
|
}
|
|
// TODO: Lock receive lock.
|
|
if ( icr & EM_INTERRUPT_RXDMT0 )
|
|
{
|
|
// TODO: Why type int? Why 16? Why not all of them?
|
|
// Load more receive descriptors
|
|
for ( int count = 0; count < 16; count++ )
|
|
{
|
|
Ref<Packet> buf = GetPacket();
|
|
if ( buf )
|
|
{
|
|
if ( !AddReceiveDescriptor(buf) )
|
|
break; // TODO: Correct handling? Data loss?
|
|
}
|
|
}
|
|
unhandled &= ~EM_INTERRUPT_RXDMT0;
|
|
}
|
|
if ( icr & EM_INTERRUPT_MDAC )
|
|
{
|
|
// MDI/O Access Complete
|
|
}
|
|
if ( icr & EM_INTERRUPT_RXT0 )
|
|
{
|
|
// Receive timer expired, check descriptors.
|
|
// TODO: Test if rdesc[rx_prochead] is defined?
|
|
while ( rx_prochead != rx_tail && rdesc[rx_prochead].status )
|
|
{
|
|
Ref<Packet> rxpacket = rpackets[rx_prochead];
|
|
rpackets[rx_prochead].Reset();
|
|
if ( !rxpacket.IsUnique() )
|
|
Log("Packet is not unique");
|
|
rxpacket->length = rdesc[rx_prochead].length;
|
|
if ( rxpacket->pmap.phys != rdesc[rx_prochead].address )
|
|
Log("Packet mismatch");
|
|
rxpacket->netif = this;
|
|
Ether::Handle(rxpacket, true);
|
|
rx_prochead++;
|
|
if ( rx_prochead >= rx_count )
|
|
rx_prochead = 0;
|
|
if ( rx_prochead != rx_tail )
|
|
{
|
|
Ref<Packet> buf = GetPacket();
|
|
if ( buf )
|
|
AddReceiveDescriptor(buf);
|
|
}
|
|
}
|
|
unhandled &= ~EM_INTERRUPT_RXT0;
|
|
}
|
|
if ( icr & EM_INTERRUPT_RXO )
|
|
{
|
|
// TODO: Receiver overrun, do we need more buffers?
|
|
Log("Rx Overrun!");
|
|
unhandled &= ~EM_INTERRUPT_RXO;
|
|
}
|
|
// TODO: Unlock receive lock.
|
|
kthread_mutex_lock(&tx_lock);
|
|
if ( icr & (EM_INTERRUPT_TXDW | EM_INTERRUPT_TXD_LOW) )
|
|
{
|
|
// Transmit descriptor written back.
|
|
// Transmit descriptor low threshold.
|
|
while ( tx_prochead != tx_tail &&
|
|
tdesc[tx_prochead].status /* TODO: Not all status bits should be waited for? */ )
|
|
{
|
|
tpackets[tx_prochead].Reset();
|
|
tx_prochead++;
|
|
if ( tx_count <= tx_prochead )
|
|
tx_prochead = 0;
|
|
}
|
|
unhandled &= ~(EM_INTERRUPT_TXDW | EM_INTERRUPT_TXD_LOW);
|
|
}
|
|
if ( icr & EM_INTERRUPT_TXQE )
|
|
{
|
|
// Transmit queue is empty. Head should equal tail.
|
|
if ( !(tx_tail < tx_count) )
|
|
PanicF("EM: transmit descriptor pointers corrupt");
|
|
while ( tx_prochead != tx_tail )
|
|
{
|
|
tpackets[tx_prochead].Reset();
|
|
tx_prochead++;
|
|
if ( tx_count <= tx_prochead )
|
|
tx_prochead = 0;
|
|
}
|
|
unhandled &= ~EM_INTERRUPT_TXQE;
|
|
}
|
|
while ( tx_queue_first && CanAddTransmit() )
|
|
{
|
|
Ref<Packet> pkt = tx_queue_first;
|
|
tx_queue_first = pkt->next;
|
|
pkt->next.Reset();
|
|
AddTransmitDescriptor(pkt);
|
|
}
|
|
kthread_mutex_unlock(&tx_lock);
|
|
interrupt_work_icr = 0;
|
|
// Unmask interrupts so they can be delivered again.
|
|
Write32(EM_MAIN_REG_IMS, understood_interrupts);
|
|
}
|
|
|
|
void EM::Reset() // TODO: This function should be able to fail.
|
|
{
|
|
uint32_t ctrl;
|
|
uint32_t status;
|
|
|
|
PCI::DisableBusMaster(devaddr);
|
|
PCI::DisableInterruptLine(devaddr);
|
|
|
|
if ( feature->pcie )
|
|
{
|
|
// For PCIe devices, disable GIO Master prior to reset.
|
|
ctrl = Read32(EM_MAIN_REG_CTRL);
|
|
ctrl |= EM_MAIN_REG_CTRL_GIOMD;
|
|
Write32(EM_MAIN_REG_CTRL, ctrl);
|
|
while ( true )
|
|
{
|
|
status = Read32(EM_MAIN_REG_STATUS);
|
|
if ( !(status & EM_MAIN_REG_STATUS_GIOME) )
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Clear all interrupts and disable rx/tx.
|
|
// TODO: Writing UINT32_MAX here is bad, document says the upper bits are
|
|
// reserved and should be set to zero.
|
|
Write32(EM_MAIN_REG_IMC, UINT32_MAX);
|
|
Write32(EM_MAIN_REG_RCTL, 0);
|
|
Write32(EM_MAIN_REG_TCTL, 0);
|
|
|
|
// Reset the device, this initializes everything to default settings
|
|
ctrl = Read32(EM_MAIN_REG_CTRL);
|
|
ctrl |= EM_MAIN_REG_CTRL_RST;
|
|
Write32(EM_MAIN_REG_CTRL, ctrl);
|
|
|
|
// TODO: The documentation mentioned waiting a short inverval here before
|
|
// checking the register again.
|
|
|
|
// Wait for it to finish.
|
|
while ( true )
|
|
{
|
|
// TODO: Timeout.
|
|
|
|
// Can exit this wait if card is done loading it's settings.
|
|
if ( Read32(EM_MAIN_REG_EECD) & EM_MAIN_REG_EECD_ARD )
|
|
break;
|
|
|
|
// Hack to make sure the control read below is valid.
|
|
// On some hardware, this loop would hang without this.
|
|
// Read all the statisics registers (which we do later anyway).
|
|
for ( uint32_t x = 0; x < 256; x += 4 )
|
|
Read32(EM_STAT_REG_CRCERRS + x);
|
|
|
|
// Read, and wait for reset to finish.
|
|
ctrl = Read32(EM_MAIN_REG_CTRL);
|
|
if ( ctrl & EM_MAIN_REG_CTRL_PHY_RST )
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
// TODO: i-o-controller-hub-8-9-10-82566-82567-82562v-software-dev-manual.pdf
|
|
// 11.4.1 says interrupts should be disabled after a reset.
|
|
// TODO: Writing UINT32_MAX here is bad, document says the upper bits are
|
|
// reserved and should be set to zero.
|
|
Write32(EM_MAIN_REG_IMC, UINT32_MAX);
|
|
|
|
if ( feature->eeprom )
|
|
{
|
|
// If we have EEPROM read the MAC directly from it.
|
|
uint16_t macw[3];
|
|
macw[0] = ReadEEPROM(EM_EEPROM_REG_ETHERNET_ADDR_1);
|
|
macw[1] = ReadEEPROM(EM_EEPROM_REG_ETHERNET_ADDR_2);
|
|
macw[2] = ReadEEPROM(EM_EEPROM_REG_ETHERNET_ADDR_3);
|
|
ifinfo.addr[0] = macw[0] >> 0 & 0xFF;
|
|
ifinfo.addr[1] = macw[0] >> 8 & 0xFF;
|
|
ifinfo.addr[2] = macw[1] >> 0 & 0xFF;
|
|
ifinfo.addr[3] = macw[1] >> 8 & 0xFF;
|
|
ifinfo.addr[4] = macw[2] >> 0 & 0xFF;
|
|
ifinfo.addr[5] = macw[2] >> 8 & 0xFF;
|
|
}
|
|
else
|
|
{
|
|
uint32_t macd[2];
|
|
// Receive Address[0] is programmed with with hardware mac
|
|
// from PROM or EEPROM after the device is reset.
|
|
macd[0] = Read32(EM_FILTER_REG_RAL);
|
|
macd[1] = Read32(EM_FILTER_REG_RAH);
|
|
ifinfo.addr[0] = macd[0] >> 0 & 0xFF;
|
|
ifinfo.addr[1] = macd[0] >> 8 & 0xFF;
|
|
ifinfo.addr[2] = macd[0] >> 16 & 0xFF;
|
|
ifinfo.addr[3] = macd[0] >> 24 & 0xFF;
|
|
ifinfo.addr[4] = macd[1] >> 0 & 0xFF;
|
|
ifinfo.addr[5] = macd[1] >> 8 & 0xFF;
|
|
}
|
|
memcpy(&cfg.ether.address, ifinfo.addr, sizeof(struct ether_addr));
|
|
|
|
// Enable bus mastering so the card can read/write memory.
|
|
PCI::EnableBusMaster(devaddr);
|
|
PCI::EnableMemoryWrite(devaddr);
|
|
|
|
status = Read32(EM_MAIN_REG_STATUS);
|
|
bool inserdes = false;
|
|
if ( feature->serdes )
|
|
inserdes = status & EM_MAIN_REG_STATUS_TBIMODE;
|
|
|
|
ctrl = Read32(EM_MAIN_REG_CTRL);
|
|
|
|
if ( inserdes )
|
|
ctrl &= ~EM_MAIN_REG_CTRL_LRST; // TBI/SerDes only.
|
|
ctrl |= EM_MAIN_REG_CTRL_ASDE;
|
|
ctrl |= EM_MAIN_REG_CTRL_SLU;
|
|
ctrl &= ~EM_MAIN_REG_CTRL_ILOS;
|
|
ctrl &= ~EM_MAIN_REG_CTRL_FRCSPD;
|
|
ctrl &= ~EM_MAIN_REG_CTRL_FRCDPLX;
|
|
ctrl &= ~EM_MAIN_REG_CTRL_VME;
|
|
// TODO: CTRL.RFCE (hub 8/9/10 pdf 11.4.3.2 says read from phy regs)
|
|
// TODO: CTRL.TFCE (hub 8/9/10 pdf 11.4.3.2 says read from phy regs)
|
|
// TODO: CTRL.ILOS
|
|
|
|
Write32(EM_MAIN_REG_CTRL, ctrl);
|
|
|
|
// CTRL.FRCSPD = CTRL.FRCDPLX = 0b; CTRL.ASDE = 1b
|
|
// CTRL.FD Duplex if FRCDPLX is set, ignored otherwise
|
|
// CTRL.SLU Enable link
|
|
// CTRL.ASDE Auto-Speed Detection Enable, ignored in TBI/Serdes mode
|
|
// CTRL.RFCE respond to reception of flow control packets.
|
|
// Set by Auto-negotiation if negotiation is enabled.
|
|
// CTRL.TFCE Ethernet controller transmits flow control packets
|
|
// based on the receive FIFO fullness, or when triggered.
|
|
// Set by Auto-negotiation if negotiation is enabled.
|
|
// CTRL.ILOS Invert Loss-of-Signal, reserved on some devices.
|
|
// set to 0.
|
|
// CTRL.SPEED Speed if FRCSPD is set, ignored otherwise.
|
|
// CTRL.VME Enable VLAN Tag removal and processing.
|
|
|
|
// STATUS.FD Reflects the value of CTRL.FD as above.
|
|
// STATUS.LU Reflects internal link status
|
|
// STATUS.SPEED Speed status bits reflect speed resolved from ASD function.
|
|
/*
|
|
For the 82541xx and 82547GI/EI , configure the LED behavior through LEDCTRL.
|
|
TODO
|
|
*/
|
|
|
|
// FCAH and FCAL should contain the flow control Ethernet address.
|
|
// 01:80:C2:00:00:01 and ethertype 0x8808
|
|
Write32(EM_MAIN_REG_FCAH, 0x0100);
|
|
Write32(EM_MAIN_REG_FCAL, 0x00c28001);
|
|
Write32(EM_MAIN_REG_FCT, 0x8808);
|
|
Write32(EM_MAIN_REG_FCTTV, 0);
|
|
|
|
// Clear all statistical counters.
|
|
for ( uint32_t x = 0; x < 256; x += 4 )
|
|
Read32(EM_STAT_REG_CRCERRS + x);
|
|
|
|
// Setup the descriptor tables
|
|
Write32(EM_MAIN_REG_RCTL, 0);
|
|
Write32(EM_MAIN_REG_TCTL, 0);
|
|
rx_tail = 0;
|
|
rx_prochead = 0;
|
|
tx_tail = 0;
|
|
tx_prochead = 0;
|
|
rx_count = rdesc_alloc.size / sizeof(struct rx_desc);
|
|
tx_count = tdesc_alloc.size / sizeof(struct tx_desc_tcpdata);
|
|
rdesc = (struct rx_desc*) rdesc_alloc.from;
|
|
tdesc = (struct tx_desc_tcpdata*) tdesc_alloc.from;
|
|
if ( !rpackets )
|
|
rpackets = new Ref<Packet>[rx_count];
|
|
if ( !tpackets )
|
|
tpackets = new Ref<Packet>[tx_count];
|
|
// TODO: Check allocation failures.
|
|
Write32(EM_MAIN_REG_RDLEN, rdesc_alloc.size);
|
|
Write32(EM_MAIN_REG_RDH, 0);
|
|
Write32(EM_MAIN_REG_RDT, 0);
|
|
Write32(EM_MAIN_REG_RDBAL, (uint64_t) rdesc_alloc.phys & 0xffffffff);
|
|
Write32(EM_MAIN_REG_RDBAH, (uint64_t) rdesc_alloc.phys >> 32);
|
|
Write32(EM_MAIN_REG_RADV, 0);
|
|
Write32(EM_MAIN_REG_RSRPD, 0);
|
|
|
|
Write32(EM_MAIN_REG_TXDCTL,
|
|
EM_MAIN_REG_TXDCTL_WTHRESH(1) | EM_MAIN_REG_TXDCTL_GRAN);
|
|
uint32_t tipg = Read32(EM_MAIN_REG_TIPG);
|
|
// TODO: Is programming TIPG needed?
|
|
tipg = EM_MAIN_REG_TIPG_IPGT(10) |
|
|
EM_MAIN_REG_TIPG_IPGR1(4) |
|
|
EM_MAIN_REG_TIPG_IPGR2(6);
|
|
tipg = EM_MAIN_REG_TIPG_IPGT(8) | /* For 82567 */
|
|
EM_MAIN_REG_TIPG_IPGR1(8) |
|
|
EM_MAIN_REG_TIPG_IPGR2(7); // Was 0x902008 on my laptop
|
|
Write32(EM_MAIN_REG_TIPG, tipg);
|
|
Write32(EM_MAIN_REG_TDLEN, tdesc_alloc.size);
|
|
Write32(EM_MAIN_REG_TDH, 0);
|
|
Write32(EM_MAIN_REG_TDT, 0);
|
|
Write32(EM_MAIN_REG_TDBAL, (uint64_t) tdesc_alloc.phys & 0xffffffff);
|
|
Write32(EM_MAIN_REG_TDBAH, (uint64_t) tdesc_alloc.phys >> 32);
|
|
|
|
for ( size_t i = 0; i < 32; i++ )
|
|
{
|
|
Ref<Packet> buf = GetPacket();
|
|
// TODO: Handle failures here.
|
|
if ( !buf )
|
|
break;
|
|
if ( !AddReceiveDescriptor(buf) )
|
|
break;
|
|
}
|
|
|
|
// Enable Receive and Transmit.
|
|
Write32(EM_MAIN_REG_RCTL, EM_MAIN_REG_RCTL_EN | EM_MAIN_REG_RCTL_SBP |
|
|
EM_MAIN_REG_RCTL_MPE | EM_MAIN_REG_RCTL_BAM | EM_MAIN_REG_RCTL_SECRC);
|
|
Write32(EM_MAIN_REG_TCTL, EM_MAIN_REG_TCTL_EN | EM_MAIN_REG_TCTL_PSP |
|
|
EM_MAIN_REG_TCTL_CT(15) | EM_MAIN_REG_TCTL_COLD(64) |
|
|
EM_MAIN_REG_TCTL_RTLC /**/| EM_MAIN_REG_TCTL_RESERVED1);
|
|
|
|
RegisterInterrupts();
|
|
PCI::EnableInterruptLine(devaddr);
|
|
// TODO: Writing UINT32_MAX here is bad, document says the upper bits are
|
|
// reserved and should be set to zero.
|
|
// Reset all the interrupt status (set all interrupts).
|
|
Write32(EM_MAIN_REG_IMS, UINT32_MAX);
|
|
// Disable all interrupts.
|
|
Write32(EM_MAIN_REG_IMC, UINT32_MAX);
|
|
// Enable relevent interrupts.
|
|
Write32(EM_MAIN_REG_IMS, understood_interrupts);
|
|
|
|
// TODO: The network can already be up in qemu without the LSC interrupt.
|
|
// This hack handles that but it's not really race condition safe with
|
|
// respect to interrupts.
|
|
status = Read32(EM_MAIN_REG_STATUS);
|
|
{
|
|
ScopedLock lock(&cfg_lock);
|
|
if ( status & EM_MAIN_REG_STATUS_LU )
|
|
ifstatus.flags |= IF_STATUS_FLAGS_UP;
|
|
else
|
|
ifstatus.flags &= ~IF_STATUS_FLAGS_UP;
|
|
kthread_cond_broadcast(&cfg_cond);
|
|
}
|
|
}
|
|
|
|
struct EM_Call
|
|
{
|
|
const char* devpath;
|
|
Ref<Descriptor> dev;
|
|
size_t devindex;
|
|
};
|
|
|
|
static bool Instance(uint32_t devaddr,
|
|
const pciid_t* /*id*/,
|
|
const pcitype_t* /*type*/,
|
|
void* u,
|
|
void* info)
|
|
{
|
|
EM_Call* em_call = (EM_Call*) u;
|
|
|
|
EM* em = new EM(devaddr);
|
|
if ( !em )
|
|
return true;
|
|
|
|
snprintf(em->ifinfo.name, sizeof(em->ifinfo.name), "em%zu",
|
|
em_call->devindex++);
|
|
|
|
pcibar_t mmio_bar = PCI::GetBAR(devaddr, 0);
|
|
if ( mmio_bar.size() < 128*1024 )
|
|
{
|
|
em->Log("Register area is too small.\n");
|
|
return errno = EINVAL, true;
|
|
}
|
|
|
|
if ( !MapPCIBAR(&em->mmio_alloc, mmio_bar, Memory::PAT_UC) )
|
|
{
|
|
em->Log("Registers could not be mapped: %m\n");
|
|
return true;
|
|
}
|
|
em->mmio_base = (volatile uint8_t*) em->mmio_alloc.from;
|
|
|
|
if ( !AllocateAndMapPage(&em->rdesc_alloc, PAGE_USAGE_DRIVER,
|
|
Memory::PAT_UC) )
|
|
{
|
|
em->Log("Could not map descriptor page.\n");
|
|
return true;
|
|
}
|
|
if ( !AllocateAndMapPage(&em->tdesc_alloc, PAGE_USAGE_DRIVER,
|
|
Memory::PAT_UC) )
|
|
{
|
|
// TODO: Memory leak of the above.
|
|
em->Log("Could not map descriptor page.\n");
|
|
return true;
|
|
}
|
|
|
|
if ( info )
|
|
em->feature = (const struct EM_Features*) info;
|
|
else
|
|
em->feature = &em_feature[emdefault];
|
|
|
|
em->Reset();
|
|
|
|
if ( !RegisterNetworkInterface(em, em_call->dev) )
|
|
PanicF("Failed to register %s as network interface", em->ifinfo.name);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Init(const char* devpath, Ref<Descriptor> dev)
|
|
{
|
|
ioctx_t ctx; SetupKernelIOCtx(&ctx);
|
|
|
|
EM_Call emcall =
|
|
{
|
|
.devpath = devpath,
|
|
.dev = dev,
|
|
.devindex = 0,
|
|
};
|
|
|
|
pcifind_t* filters = new pcifind_t[filter_count];
|
|
if ( !filters )
|
|
Panic("[em] Failed to allocate PCI filters");
|
|
for ( size_t index = 0; index < filter_count; index++ )
|
|
{
|
|
const struct EM_Features* feature =
|
|
&em_feature[em_table[index].feature_index];
|
|
uint32_t device_id = em_table[index].device_id;
|
|
// TODO: Name these constants.
|
|
filters[index] = pcifind_t((void*) feature, 0x8086, device_id,
|
|
0x02, 0x00, 0x00);
|
|
}
|
|
|
|
PCI::Search(Instance, &emcall, filters, filter_count);
|
|
delete[] filters;
|
|
}
|
|
|
|
} // namespace EM
|
|
} // namespace Sortix
|