|
@@ -0,0 +1,389 @@
|
|
|
+#include <defs.hpp>
|
|
|
+
|
|
|
+#include <assert.h>
|
|
|
+#include <errno.h>
|
|
|
+
|
|
|
+#include <driver/e1000e.hpp>
|
|
|
+#include <kernel/hw/pci.hpp>
|
|
|
+#include <kernel/irq.hpp>
|
|
|
+#include <kernel/log.hpp>
|
|
|
+#include <kernel/mem/paging.hpp>
|
|
|
+#include <kernel/mem/phys.hpp>
|
|
|
+#include <kernel/module.hpp>
|
|
|
+#include <net/arp.hpp>
|
|
|
+#include <net/ethernet.hpp>
|
|
|
+#include <net/netdev.hpp>
|
|
|
+
|
|
|
+using namespace kernel::kmod;
|
|
|
+using namespace kernel::hw::pci;
|
|
|
+using namespace kernel::mem;
|
|
|
+using namespace net;
|
|
|
+
|
|
|
+using namespace hw::e1000e;
|
|
|
+
|
|
|
+constexpr int E1000E_RX_DESC_COUNT = 32;
|
|
|
+constexpr int E1000E_TX_DESC_COUNT = 32;
|
|
|
+
|
|
|
+class e1000eDevice : public virtual Netdev {
|
|
|
+ u8* base;
|
|
|
+
|
|
|
+ // rx at 0x0000, tx at 0x0200
|
|
|
+ paging::page* rtDescriptorPage;
|
|
|
+
|
|
|
+ int rxHead;
|
|
|
+ int rxTail;
|
|
|
+
|
|
|
+ int txHead;
|
|
|
+ int txTail;
|
|
|
+
|
|
|
+ void* at(long offset) const;
|
|
|
+ void write(long offset, u32 value) const;
|
|
|
+ u32 read(long offset) const;
|
|
|
+
|
|
|
+ physaddr<RxDescriptor> rxDescriptors() const;
|
|
|
+ physaddr<TxDescriptor> txDescriptors() const;
|
|
|
+
|
|
|
+ virtual int up() override;
|
|
|
+ virtual isize send(const u8* buffer, usize length) override;
|
|
|
+
|
|
|
+ int reset();
|
|
|
+ int getMacAddress();
|
|
|
+ int clearStats();
|
|
|
+ int clearRxTxDescriptorTable();
|
|
|
+
|
|
|
+ int setupRx();
|
|
|
+ int setupTx();
|
|
|
+
|
|
|
+ public:
|
|
|
+ static int probe(pci_device& dev);
|
|
|
+
|
|
|
+ e1000eDevice(pci_device& dev, u8* base);
|
|
|
+ virtual ~e1000eDevice();
|
|
|
+};
|
|
|
+
|
|
|
+e1000eDevice::e1000eDevice(pci_device& dev, u8* base)
|
|
|
+ : Netdev(dev)
|
|
|
+ , base(base)
|
|
|
+ , rtDescriptorPage(paging::alloc_page())
|
|
|
+ , rxHead(-1)
|
|
|
+ , rxTail(-1)
|
|
|
+ , txHead(-1)
|
|
|
+ , txTail(-1) {
|
|
|
+ auto pPage = physaddr<u8>{paging::page_to_pfn(rtDescriptorPage)};
|
|
|
+ memset(pPage, 0, 0x1000);
|
|
|
+
|
|
|
+ auto pRxDescriptors = this->rxDescriptors();
|
|
|
+ for (int i = 0; i < E1000E_RX_DESC_COUNT; i++) {
|
|
|
+ auto& desc = pRxDescriptors[i];
|
|
|
+
|
|
|
+ auto bufPage = paging::alloc_pages(2);
|
|
|
+ desc.bufferAddress = paging::page_to_pfn(bufPage);
|
|
|
+ }
|
|
|
+
|
|
|
+ clearRxTxDescriptorTable();
|
|
|
+
|
|
|
+ getMacAddress();
|
|
|
+}
|
|
|
+
|
|
|
+// TODO: make sure all transactions are complete before freeing resources
|
|
|
+e1000eDevice::~e1000eDevice() {
|
|
|
+ auto pRxDescriptors = this->rxDescriptors();
|
|
|
+ for (int i = 0; i < E1000E_RX_DESC_COUNT; i++) {
|
|
|
+ auto& desc = pRxDescriptors[i];
|
|
|
+
|
|
|
+ auto* bufPage = paging::pfn_to_page(desc.bufferAddress);
|
|
|
+ paging::free_pages(bufPage, 2);
|
|
|
+
|
|
|
+ desc.bufferAddress = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ paging::free_page(rtDescriptorPage);
|
|
|
+}
|
|
|
+
|
|
|
+void* e1000eDevice::at(long offset) const {
|
|
|
+ return base + offset;
|
|
|
+}
|
|
|
+
|
|
|
+void e1000eDevice::write(long offset, u32 value) const {
|
|
|
+ *reinterpret_cast<u32 volatile*>(at(offset)) = value;
|
|
|
+}
|
|
|
+
|
|
|
+u32 e1000eDevice::read(long offset) const {
|
|
|
+ return *reinterpret_cast<u32 volatile*>(at(offset));
|
|
|
+}
|
|
|
+
|
|
|
+physaddr<RxDescriptor> e1000eDevice::rxDescriptors() const {
|
|
|
+ auto pfn = paging::page_to_pfn(rtDescriptorPage);
|
|
|
+ return physaddr<RxDescriptor>(pfn);
|
|
|
+}
|
|
|
+
|
|
|
+physaddr<TxDescriptor> e1000eDevice::txDescriptors() const {
|
|
|
+ auto pfn = paging::page_to_pfn(rtDescriptorPage);
|
|
|
+ return physaddr<TxDescriptor>(pfn + 0x200);
|
|
|
+}
|
|
|
+
|
|
|
+int e1000eDevice::reset() {
|
|
|
+ // disable interrupts
|
|
|
+ write(REG_IMC, 0xffffffff);
|
|
|
+
|
|
|
+ // pcie master disable
|
|
|
+ u32 ctrl = read(REG_CTRL);
|
|
|
+ ctrl |= CTRL_GIOD;
|
|
|
+ write(REG_CTRL, ctrl);
|
|
|
+
|
|
|
+ while (test(read(REG_STATUS), STATUS_GIOE))
|
|
|
+ ;
|
|
|
+
|
|
|
+ ctrl = read(REG_CTRL);
|
|
|
+ ctrl |= CTRL_RST;
|
|
|
+ write(REG_CTRL, ctrl);
|
|
|
+
|
|
|
+ while (test(read(REG_CTRL), CTRL_RST))
|
|
|
+ ;
|
|
|
+
|
|
|
+ // disable interrupts again
|
|
|
+ write(REG_IMC, 0xffffffff);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int e1000eDevice::getMacAddress() {
|
|
|
+ memcpy(&mac, at(0x5400), 6);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int e1000eDevice::setupRx() {
|
|
|
+ auto pRxDescriptors = this->rxDescriptors();
|
|
|
+
|
|
|
+ write(REG_RDBAL, pRxDescriptors.phys() & 0xffffffff);
|
|
|
+ write(REG_RDBAH, pRxDescriptors.phys() >> 32);
|
|
|
+
|
|
|
+ write(REG_RDLEN, E1000E_RX_DESC_COUNT * sizeof(RxDescriptor));
|
|
|
+
|
|
|
+ write(REG_RDH, 0);
|
|
|
+ rxHead = 0;
|
|
|
+
|
|
|
+ write(REG_RDT, E1000E_RX_DESC_COUNT - 1);
|
|
|
+ rxTail = E1000E_RX_DESC_COUNT - 1;
|
|
|
+
|
|
|
+ write(REG_RCTL, RCTL_EN | RCTL_UPE | RCTL_MPE | RCTL_LPE | RCTL_LBM_NO |
|
|
|
+ RCTL_DTYP_LEGACY | RCTL_BAM | RCTL_BSIZE_8192 |
|
|
|
+ RCTL_SECRC);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int e1000eDevice::setupTx() {
|
|
|
+ auto pTxDescriptors = this->txDescriptors();
|
|
|
+
|
|
|
+ write(REG_TDBAL, pTxDescriptors.phys() & 0xffffffff);
|
|
|
+ write(REG_TDBAH, pTxDescriptors.phys() >> 32);
|
|
|
+
|
|
|
+ write(REG_TDLEN, E1000E_TX_DESC_COUNT * sizeof(TxDescriptor));
|
|
|
+
|
|
|
+ write(REG_TDH, 0);
|
|
|
+ txHead = 0;
|
|
|
+
|
|
|
+ write(REG_TDT, 0);
|
|
|
+ txTail = 0;
|
|
|
+
|
|
|
+ write(REG_TCTL, TCTL_EN | TCTL_PSP | (15 << TCTL_CT_SHIFT) |
|
|
|
+ (64 << TCTL_COLD_SHIFT) | TCTL_RTLC);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int e1000eDevice::up() {
|
|
|
+ // set link up
|
|
|
+ u32 ctrl = read(REG_CTRL);
|
|
|
+ u32 stat = read(REG_STATUS);
|
|
|
+
|
|
|
+ if (!test(ctrl, CTRL_SLU) || !test(stat, STATUS_LU))
|
|
|
+ return -EIO;
|
|
|
+
|
|
|
+ // auto negotiation speed
|
|
|
+ switch (stat & STATUS_SPEED_MASK) {
|
|
|
+ case STATUS_SPEED_10M:
|
|
|
+ setLinkSpeed(NETDEV_SPEED_10M);
|
|
|
+ break;
|
|
|
+ case STATUS_SPEED_100M:
|
|
|
+ setLinkSpeed(NETDEV_SPEED_100M);
|
|
|
+ break;
|
|
|
+ case STATUS_SPEED_1000M:
|
|
|
+ setLinkSpeed(NETDEV_SPEED_1000M);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ // clear multicast table
|
|
|
+ for (int i = 0; i < 128; i += 4)
|
|
|
+ write(0x5200 + i, 0);
|
|
|
+
|
|
|
+ clearStats();
|
|
|
+
|
|
|
+ // enable interrupts
|
|
|
+ write(REG_IMS, ICR_NORMAL | ICR_UP);
|
|
|
+
|
|
|
+ // read to clear any pending interrupts
|
|
|
+ read(REG_ICR);
|
|
|
+
|
|
|
+ kernel::irq::register_handler(
|
|
|
+ device.header_type0().interrupt_line, [this]() {
|
|
|
+ auto cause = read(REG_ICR);
|
|
|
+
|
|
|
+ if (!test(cause, ICR_INT))
|
|
|
+ return;
|
|
|
+
|
|
|
+ rxHead = read(REG_RDH);
|
|
|
+
|
|
|
+ auto nextTail = (rxTail + 1) % E1000E_RX_DESC_COUNT;
|
|
|
+ while (nextTail != rxHead) {
|
|
|
+ auto pRxDescriptors = rxDescriptors();
|
|
|
+ auto& desc = pRxDescriptors[nextTail];
|
|
|
+
|
|
|
+ assert(desc.status & RXD_STAT_DD);
|
|
|
+
|
|
|
+ auto pBuf = physaddr<u8>{desc.bufferAddress};
|
|
|
+ kmsg("==== e1000e: received packet ====");
|
|
|
+
|
|
|
+ char buf[256];
|
|
|
+ for (int i = 0; i < desc.length; i++) {
|
|
|
+ if (i && i % 16 == 0)
|
|
|
+ kmsg("");
|
|
|
+
|
|
|
+ snprintf(buf, sizeof(buf), "%x ", pBuf[i]);
|
|
|
+ kernel::tty::console->print(buf);
|
|
|
+ }
|
|
|
+
|
|
|
+ kmsg("\n==== e1000e: end of packet ====");
|
|
|
+
|
|
|
+ desc.status = 0;
|
|
|
+ rxTail = nextTail;
|
|
|
+ nextTail = (nextTail + 1) % E1000E_RX_DESC_COUNT;
|
|
|
+ }
|
|
|
+
|
|
|
+ write(REG_RDT, rxTail);
|
|
|
+ });
|
|
|
+
|
|
|
+ int ret = setupRx();
|
|
|
+ if (ret != 0)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ ret = setupTx();
|
|
|
+ if (ret != 0)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ status &= ~NETDEV_DOWN;
|
|
|
+ status |= NETDEV_UP;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+isize e1000eDevice::send(const u8* buffer, usize length) {
|
|
|
+ auto txTailNext = (txTail + 1) % E1000E_TX_DESC_COUNT;
|
|
|
+ if (txTailNext == txHead)
|
|
|
+ return -EAGAIN;
|
|
|
+
|
|
|
+ auto pTxDescriptors = this->txDescriptors();
|
|
|
+ auto& desc = pTxDescriptors[txTail];
|
|
|
+
|
|
|
+ if (!(desc.status & TXD_STAT_DD))
|
|
|
+ return -EIO;
|
|
|
+
|
|
|
+ auto bufPage = paging::alloc_page();
|
|
|
+ auto pPage = physaddr<u8>{paging::page_to_pfn(bufPage)};
|
|
|
+ memcpy(pPage, buffer, length);
|
|
|
+
|
|
|
+ desc.bufferAddress = pPage.phys();
|
|
|
+ desc.length = length;
|
|
|
+ desc.cmd = TXD_CMD_EOP | TXD_CMD_IFCS | TXD_CMD_RS;
|
|
|
+ desc.status = 0;
|
|
|
+
|
|
|
+ txTail = txTailNext;
|
|
|
+ write(REG_TDT, txTailNext);
|
|
|
+
|
|
|
+ while (!test(desc.status, TXD_STAT_DD))
|
|
|
+ ;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int e1000eDevice::clearStats() {
|
|
|
+ write(REG_COLC, 0);
|
|
|
+ write(REG_GPRC, 0);
|
|
|
+ write(REG_MPRC, 0);
|
|
|
+ write(REG_GPTC, 0);
|
|
|
+ write(REG_GORCL, 0);
|
|
|
+ write(REG_GORCH, 0);
|
|
|
+ write(REG_GOTCL, 0);
|
|
|
+ write(REG_GOTCH, 0);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int e1000eDevice::clearRxTxDescriptorTable() {
|
|
|
+ auto pRxDescriptors = this->rxDescriptors();
|
|
|
+ for (int i = 0; i < E1000E_RX_DESC_COUNT; i++) {
|
|
|
+ auto& desc = pRxDescriptors[i];
|
|
|
+ desc.status = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ auto pTxDescriptors = this->txDescriptors();
|
|
|
+ for (int i = 0; i < E1000E_TX_DESC_COUNT; i++) {
|
|
|
+ auto& desc = pTxDescriptors[i];
|
|
|
+ desc.status = TXD_STAT_DD;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int e1000eDevice::probe(pci_device& dev) {
|
|
|
+ auto bar0 = dev.header_type0().bars[0];
|
|
|
+ if ((bar0 & 0xf) != 0)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ dev.enableBusMastering();
|
|
|
+
|
|
|
+ auto baseAddress = physaddr<uint8_t, false>{bar0 & ~0xf};
|
|
|
+ auto e1000ePointer = std::make_unique<e1000eDevice>(dev, baseAddress);
|
|
|
+
|
|
|
+ int ret = e1000ePointer->reset();
|
|
|
+ if (ret != 0)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ ret = e1000ePointer->up();
|
|
|
+ if (ret != 0)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ return registerNetdev(std::move(e1000ePointer));
|
|
|
+}
|
|
|
+
|
|
|
+class e1000e_module : public virtual kmod {
|
|
|
+ public:
|
|
|
+ e1000e_module() : kmod("e1000e") {}
|
|
|
+
|
|
|
+ virtual int init() override {
|
|
|
+ const int device_id[] = {
|
|
|
+ 0x100e,
|
|
|
+ 0x10d3,
|
|
|
+ 0x10ea,
|
|
|
+ 0x153a,
|
|
|
+ };
|
|
|
+
|
|
|
+ for (auto id : device_id) {
|
|
|
+ auto ret = kernel::hw::pci::register_driver(0x8086, id,
|
|
|
+ e1000eDevice::probe);
|
|
|
+
|
|
|
+ // TODO: in case any of the devices fail to register,
|
|
|
+ // we should return an error code and cleanup these
|
|
|
+ // device drivers
|
|
|
+ if (ret != 0)
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+INTERNAL_MODULE(e1000e, e1000e_module);
|