/*
 * Copyright (c) 2011-2017 Jonas 'Sortie' Termansen.
 *
 * 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.
 *
 * sortix/kernel/pci.h
 * Functions for handling PCI devices.
 */

#ifndef _INCLUDE_SORTIX_KERNEL_PCI_H
#define _INCLUDE_SORTIX_KERNEL_PCI_H

#include <endian.h>
#include <stdint.h>

namespace Sortix {

typedef struct
{
	uint16_t deviceid;
	uint16_t vendorid;
} pciid_t;

typedef struct
{
	uint8_t classid;
	uint8_t subclassid;
	uint8_t progif;
	uint8_t revid;
} pcitype_t;

// memset(&pcifind, 255, sizeof(pcifind)) and fill out rest.
struct pcifind_t
{
public:
	pcifind_t() {}
	constexpr pcifind_t(void* context_, uint16_t vendorid_, uint16_t deviceid_,
	                    uint8_t classid_ = 0xff, uint8_t subclassid_ = 0xff,
	                    uint8_t progif_ = 0xff, uint8_t revid_ = 0xff) :
		context(context_), vendorid(vendorid_), deviceid(deviceid_),
		classid(classid_), subclassid(subclassid_), progif(progif_),
		revid(revid_)
	{
	}

public:
	void* context;
	uint16_t vendorid;
	uint16_t deviceid;
	uint8_t classid;
	uint8_t subclassid;
	uint8_t progif;
	uint8_t revid;
};

static const uint8_t PCIBAR_TYPE_IOSPACE = 0x0 << 1 | 0x1 << 0;
static const uint8_t PCIBAR_TYPE_16BIT = 0x1 << 1 | 0x0 << 0;
static const uint8_t PCIBAR_TYPE_32BIT = 0x0 << 1 | 0x0 << 0;
static const uint8_t PCIBAR_TYPE_64BIT = 0x2 << 1 | 0x0 << 0;

typedef struct
{
public:
	uint64_t addr_raw;
	uint64_t size_raw;

public:
	uint64_t addr() const { return is_iospace() ?
	                        addr_raw & 0xFFFFFFFFFFFFFFFC :
	                        addr_raw & 0xFFFFFFFFFFFFFFF0; }
	uint64_t size() const { return size_raw & 0xFFFFFFFFFFFFFFFF; }
	uint8_t type() const { return (addr_raw & 3) == PCIBAR_TYPE_IOSPACE ?
	                              (addr_raw & 3) : (addr_raw & 0x7);  }
	uint32_t ioaddr() const { return addr_raw & 0xFFFFFFFC; };
	bool is_prefetchable() const { return addr_raw & 0x8; }
	bool is_iospace() const { return type() == PCIBAR_TYPE_IOSPACE; }
	bool is_16bit() const { return type() == PCIBAR_TYPE_16BIT; }
	bool is_32bit() const { return type() == PCIBAR_TYPE_32BIT; }
	bool is_64bit() const { return type() == PCIBAR_TYPE_64BIT; }
	bool is_mmio() const { return is_16bit() || is_32bit() || is_64bit(); }
} pcibar_t;

static const uint8_t PCIFIELD_VENDOR_ID = 0x00;
static const uint8_t PCIFIELD_DEVICE_ID = 0x02;
static const uint8_t PCIFIELD_COMMAND = 0x04;
static const uint8_t PCIFIELD_STATUS = 0x06;
static const uint8_t PCIFIELD_REVISION_ID = 0x08;
static const uint8_t PCIFIELD_PROG_IF = 0x09;
static const uint8_t PCIFIELD_SUBCLASS = 0x0A;
static const uint8_t PCIFIELD_CLASS = 0x0B;
static const uint8_t PCIFIELD_CACHE_LINE_SIZE = 0x0C;
static const uint8_t PCIFIELD_LATENCY_TIMER = 0x0D;
static const uint8_t PCIFIELD_HEADER_TYPE = 0x0E;
static const uint8_t PCIFIELD_BIST = 0x0F;
static const uint8_t PCIFIELD_RAW_BAR0 = 0x10;
static const uint8_t PCIFIELD_RAW_BAR1 = 0x14;
static const uint8_t PCIFIELD_RAW_BAR2 = 0x18;
static const uint8_t PCIFIELD_PRIMARY_BUS_NUMBER = 0x18;
static const uint8_t PCIFIELD_SECONDARY_BUS_NUMBER = 0x19;
static const uint8_t PCIFIELD_SUBORDINATE_BUS_NUMBER = 0x1A;
static const uint8_t PCIFIELD_SECONDARY_LATENCY_TIMER = 0x1B;
static const uint8_t PCIFIELD_RAW_BAR3 = 0x1C;
static const uint8_t PCIFIELD_IO_BASE = 0x1C;
static const uint8_t PCIFIELD_IO_LIMIT = 0x1D;
static const uint8_t PCIFIELD_SECONDARY_STATUS = 0x1E;
static const uint8_t PCIFIELD_RAW_BAR4 = 0x20;
static const uint8_t PCIFIELD_MEMORY_BASE = 0x20;
static const uint8_t PCIFIELD_MEMORY_LIMIT = 0x22;
static const uint8_t PCIFIELD_RAW_BAR5 = 0x24;
static const uint8_t PCIFIELD_PREFETCHABLE_MEMORY_BASE = 0x24;
static const uint8_t PCIFIELD_PREFETCHABLE_MEMORY_LIMIT = 0x26;
static const uint8_t PCIFIELD_CARDBUS_CIS_POINTER = 0x28;
static const uint8_t PCIFIELD_PREFETCHABLE_BASE_UPPER_BITS = 0x28;
static const uint8_t PCIFIELD_SUBSYSTEM_VENDOR_ID = 0x2C;
static const uint8_t PCIFIELD_PREFETCHABLE_LIMIT_UPPER_BITS = 0x2C;
static const uint8_t PCIFIELD_SUBSYSTEM_ID = 0x2E;
static const uint8_t PCIFIELD_EXPANSION_ROM_BASE_ADDRESS = 0x30;
static const uint8_t PCIFIELD_CAPABILITIES = 0x34;
static const uint8_t PCIFIELD_EXPANSION_ROM_BASE_ADDRESS_PCI_BRIDGE = 0x38;
static const uint8_t PCIFIELD_INTERRUPT_LINE = 0x3C;
static const uint8_t PCIFIELD_INTERRUPT_PIN = 0x3D;
static const uint8_t PCIFIELD_MIN_GRANT = 0x3E;
static const uint8_t PCIFIELD_MAX_LATENCY = 0x3F;
static const uint8_t PCIFIELD_BRIDGE_CONTROL = 0x3E;

static const uint16_t PCIFIELD_COMMAND_IO_SPACE = 1 << 0;
static const uint16_t PCIFIELD_COMMAND_MEMORY_SPACE = 1 << 1;
static const uint16_t PCIFIELD_COMMAND_BUS_MASTER = 1 << 2;
static const uint16_t PCIFIELD_COMMAND_SPECIAL_CYCLES = 1 << 3;
static const uint16_t PCIFIELD_COMMAND_MEMORY_WRITE_AND_INVALIDATE = 1 << 4;
static const uint16_t PCIFIELD_COMMAND_VGA_PALETTE_SNOOP = 1 << 5;
static const uint16_t PCIFIELD_COMMAND_PARITY_ERROR_RESPONSE = 1 << 6;
static const uint16_t PCIFIELD_COMMAND_SERR = 1 << 8;
static const uint16_t PCIFIELD_COMMAND_FAST_BACK_TO_BACK = 1 << 9;
static const uint16_t PCIFIELD_COMMAND_INTERRUPT_DISABLE = 1 << 10;

namespace PCI {

void Init();
uint32_t MakeDevAddr(uint8_t bus, uint8_t slot, uint8_t func);
void SplitDevAddr(uint32_t devaddr, uint8_t* vals /* bus, slot, func */);
uint8_t Read8(uint32_t devaddr, uint8_t off); // Host endian
uint16_t Read16(uint32_t devaddr, uint8_t off); // Host endian
uint32_t Read32(uint32_t devaddr, uint8_t off); // Host endian
uint32_t ReadRaw32(uint32_t devaddr, uint8_t off); // PCI endian
void Write8(uint32_t devaddr, uint8_t off, uint8_t val); // Host endian
void Write16(uint32_t devaddr, uint8_t off, uint16_t val); // Host endian
void Write32(uint32_t devaddr, uint8_t off, uint32_t val); // Host endian
void WriteRaw32(uint32_t devaddr, uint8_t off, uint32_t val); // PCI endian
pciid_t GetDeviceId(uint32_t devaddr);
pcitype_t GetDeviceType(uint32_t devaddr);
void Search(bool (*callback)(uint32_t devaddr,
                             const pciid_t* id,
                             const pcitype_t* type,
                             void* context,
                             void* pattern_context),
            void* context,
            const pcifind_t* patterns,
            size_t pattern_count);
uint32_t SearchForDevices(pcifind_t pcifind, uint32_t last = 0);
pcibar_t GetBAR(uint32_t devaddr, uint8_t bar);
pcibar_t GetExpansionROM(uint32_t devaddr);
void EnableExpansionROM(uint32_t devaddr);
void DisableExpansionROM(uint32_t devaddr);
bool IsExpansionROMEnabled(uint32_t devaddr);
uint8_t SetupInterruptLine(uint32_t devaddr);
void EnableBusMaster(uint32_t devaddr);
void DisableBusMaster(uint32_t devaddr);
void EnableMemoryWrite(uint32_t devaddr);
void DisableMemoryWrite(uint32_t devaddr);
void EnableInterruptLine(uint32_t devaddr);
void DisableInterruptLine(uint32_t devaddr);
uint8_t GetInterruptIndex(uint32_t devaddr);

} // namespace PCI
} // namespace Sortix

#endif
