577 lines
13 KiB
C
577 lines
13 KiB
C
/*
|
|
* Copyright (c) 2009 Corey Tabaka
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files
|
|
* (the "Software"), to deal in the Software without restriction,
|
|
* including without limitation the rights to use, copy, modify, merge,
|
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
* and to permit persons to whom the Software is furnished to do so,
|
|
* subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
#include <debug.h>
|
|
#include <err.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <kernel/thread.h>
|
|
#include <arch/x86/descriptor.h>
|
|
#include <dev/pci.h>
|
|
|
|
static int last_bus = 0;
|
|
|
|
typedef struct {
|
|
uint16_t size;
|
|
void *offset;
|
|
uint16_t selector;
|
|
} __PACKED irq_routing_options_t;
|
|
|
|
static int pci_type1_detect(void);
|
|
static int pci_bios_detect(void);
|
|
|
|
int pci_get_last_bus(void)
|
|
{
|
|
return last_bus;
|
|
}
|
|
|
|
/*
|
|
* pointers to installed PCI routines
|
|
*/
|
|
int (*g_pci_find_pci_device)(pci_location_t *state, uint16_t device_id, uint16_t vendor_id, uint16_t index);
|
|
int (*g_pci_find_pci_class_code)(pci_location_t *state, uint32_t class_code, uint16_t index);
|
|
|
|
int (*g_pci_read_config_byte)(const pci_location_t *state, uint32_t reg, uint8_t *value);
|
|
int (*g_pci_read_config_half)(const pci_location_t *state, uint32_t reg, uint16_t *value);
|
|
int (*g_pci_read_config_word)(const pci_location_t *state, uint32_t reg, uint32_t *value);
|
|
|
|
int (*g_pci_write_config_byte)(const pci_location_t *state, uint32_t reg, uint8_t value);
|
|
int (*g_pci_write_config_half)(const pci_location_t *state, uint32_t reg, uint16_t value);
|
|
int (*g_pci_write_config_word)(const pci_location_t *state, uint32_t reg, uint32_t value);
|
|
|
|
int (*g_pci_get_irq_routing_options)(irq_routing_options_t *options, uint16_t *pci_irqs);
|
|
int (*g_pci_set_irq_hw_int)(const pci_location_t *state, uint8_t int_pin, uint8_t irq);
|
|
|
|
|
|
int pci_find_pci_device(pci_location_t *state, uint16_t device_id, uint16_t vendor_id, uint16_t index)
|
|
{
|
|
enter_critical_section();
|
|
|
|
int res = g_pci_find_pci_device(state, device_id, vendor_id, index);
|
|
|
|
exit_critical_section();
|
|
|
|
return res;
|
|
}
|
|
|
|
int pci_find_pci_class_code(pci_location_t *state, uint32_t class_code, uint16_t index)
|
|
{
|
|
enter_critical_section();
|
|
|
|
int res = g_pci_find_pci_class_code(state, class_code, index);
|
|
|
|
exit_critical_section();
|
|
|
|
return res;
|
|
}
|
|
|
|
int pci_read_config_byte(const pci_location_t *state, uint32_t reg, uint8_t *value)
|
|
{
|
|
enter_critical_section();
|
|
|
|
int res = g_pci_read_config_byte(state, reg, value);
|
|
|
|
exit_critical_section();
|
|
|
|
return res;
|
|
}
|
|
int pci_read_config_half(const pci_location_t *state, uint32_t reg, uint16_t *value)
|
|
{
|
|
enter_critical_section();
|
|
|
|
int res = g_pci_read_config_half(state, reg, value);
|
|
|
|
exit_critical_section();
|
|
|
|
return res;
|
|
}
|
|
|
|
int pci_read_config_word(const pci_location_t *state, uint32_t reg, uint32_t *value)
|
|
{
|
|
enter_critical_section();
|
|
|
|
int res = g_pci_read_config_word(state, reg, value);
|
|
|
|
exit_critical_section();
|
|
|
|
return res;
|
|
}
|
|
|
|
int pci_write_config_byte(const pci_location_t *state, uint32_t reg, uint8_t value)
|
|
{
|
|
enter_critical_section();
|
|
|
|
int res = g_pci_write_config_byte(state, reg, value);
|
|
|
|
exit_critical_section();
|
|
|
|
return res;
|
|
}
|
|
|
|
int pci_write_config_half(const pci_location_t *state, uint32_t reg, uint16_t value)
|
|
{
|
|
enter_critical_section();
|
|
|
|
int res = g_pci_write_config_half(state, reg, value);
|
|
|
|
exit_critical_section();
|
|
|
|
return res;
|
|
}
|
|
|
|
int pci_write_config_word(const pci_location_t *state, uint32_t reg, uint32_t value)
|
|
{
|
|
enter_critical_section();
|
|
|
|
int res = g_pci_write_config_word(state, reg, value);
|
|
|
|
exit_critical_section();
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
int pci_get_irq_routing_options(irq_routing_entry *entries, uint16_t *count, uint16_t *pci_irqs)
|
|
{
|
|
enter_critical_section();
|
|
|
|
irq_routing_options_t options;
|
|
options.size = sizeof(irq_routing_entry) * *count;
|
|
options.selector = DATA_SELECTOR;
|
|
options.offset = entries;
|
|
|
|
int res = g_pci_get_irq_routing_options(&options, pci_irqs);
|
|
|
|
*count = options.size / sizeof(irq_routing_entry);
|
|
|
|
exit_critical_section();
|
|
|
|
return res;
|
|
}
|
|
|
|
int pci_set_irq_hw_int(const pci_location_t *state, uint8_t int_pin, uint8_t irq)
|
|
{
|
|
enter_critical_section();
|
|
|
|
int res = g_pci_set_irq_hw_int(state, int_pin, irq);
|
|
|
|
exit_critical_section();
|
|
|
|
return res;
|
|
}
|
|
|
|
void pci_init(void)
|
|
{
|
|
if (!pci_bios_detect()) {
|
|
dprintf(INFO, "pci bios functions installed\n");
|
|
dprintf(INFO, "last pci bus is %d\n", last_bus);
|
|
}
|
|
}
|
|
|
|
#define PCIBIOS_PRESENT 0xB101
|
|
#define PCIBIOS_FIND_PCI_DEVICE 0xB102
|
|
#define PCIBIOS_FIND_PCI_CLASS_CODE 0xB103
|
|
#define PCIBIOS_GENERATE_SPECIAL_CYCLE 0xB106
|
|
#define PCIBIOS_READ_CONFIG_BYTE 0xB108
|
|
#define PCIBIOS_READ_CONFIG_WORD 0xB109
|
|
#define PCIBIOS_READ_CONFIG_DWORD 0xB10A
|
|
#define PCIBIOS_WRITE_CONFIG_BYTE 0xB10B
|
|
#define PCIBIOS_WRITE_CONFIG_WORD 0xB10C
|
|
#define PCIBIOS_WRITE_CONFIG_DWORD 0xB10D
|
|
#define PCIBIOS_GET_IRQ_ROUTING_OPTIONS 0xB10E
|
|
#define PCIBIOS_PCI_SET_IRQ_HW_INT 0xB10F
|
|
|
|
#define PCIBIOS_SUCCESSFUL 0x00
|
|
#define PCIBIOS_FUNC_NOT_SUPPORTED 0x81
|
|
#define PCIBIOS_BAD_VENDOR_ID 0x83
|
|
#define PCIBIOS_DEVICE_NOT_FOUND 0x86
|
|
#define PCIBIOS_BAD_REGISTER_NUMBER 0x87
|
|
#define PCIBIOS_SET_FAILED 0x88
|
|
#define PCIBIOS_BUFFER_TOO_SMALL 0x89
|
|
|
|
/*
|
|
* far call structure used by BIOS32 routines
|
|
*/
|
|
static struct {
|
|
uint32_t offset;
|
|
uint16_t selector;
|
|
} __PACKED bios32_entry;
|
|
|
|
/*
|
|
* BIOS32 entry header
|
|
*/
|
|
typedef struct {
|
|
uint8_t magic[4]; // "_32_"
|
|
void * entry; // entry point
|
|
uint8_t revision;
|
|
uint8_t length;
|
|
uint8_t checksum;
|
|
uint8_t reserved[5];
|
|
} __PACKED pci_bios_info;
|
|
|
|
/*
|
|
* scan for pci bios
|
|
*/
|
|
static const char * pci_bios_magic = "_32_";
|
|
static pci_bios_info *find_pci_bios_info(void) {
|
|
uint32_t *head = (uint32_t *) 0x000e0000;
|
|
int8_t sum, *b;
|
|
uint i;
|
|
|
|
while (head < (uint32_t *) 0x000ffff0) {
|
|
if (*head == *(uint32_t *) pci_bios_magic) {
|
|
// perform the checksum
|
|
sum = 0;
|
|
b = (int8_t *) head;
|
|
for (i=0; i < sizeof(pci_bios_info); i++) {
|
|
sum += b[i];
|
|
}
|
|
|
|
if (sum == 0) {
|
|
return (pci_bios_info *) head;
|
|
}
|
|
}
|
|
|
|
head += 4;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* local BIOS32 PCI routines
|
|
*/
|
|
static int bios_find_pci_device(pci_location_t *state, uint16_t device_id, uint16_t vendor_id, uint16_t index)
|
|
{
|
|
uint32_t bx, ret;
|
|
|
|
__asm__(
|
|
"lcall *(%%edi) \n\t"
|
|
"jc 1f \n\t"
|
|
"xor %%ah,%%ah \n"
|
|
"1:"
|
|
: "=b"(bx),
|
|
"=a"(ret)
|
|
: "1"(PCIBIOS_FIND_PCI_DEVICE),
|
|
"c"(device_id),
|
|
"d"(vendor_id),
|
|
"S"(index),
|
|
"D"(&bios32_entry));
|
|
|
|
state->bus = bx >> 8;
|
|
state->dev_fn = bx & 0xFF;
|
|
|
|
ret >>= 8;
|
|
return ret & 0xFF;
|
|
}
|
|
|
|
static int bios_find_pci_class_code(pci_location_t *state, uint32_t class_code, uint16_t index)
|
|
{
|
|
uint32_t bx, ret;
|
|
|
|
__asm__(
|
|
"lcall *(%%edi) \n\t"
|
|
"jc 1f \n\t"
|
|
"xor %%ah,%%ah \n"
|
|
"1:"
|
|
: "=b"(bx),
|
|
"=a"(ret)
|
|
: "1"(PCIBIOS_FIND_PCI_CLASS_CODE),
|
|
"c"(class_code),
|
|
"S"(index),
|
|
"D"(&bios32_entry));
|
|
|
|
state->bus = bx >> 8;
|
|
state->dev_fn = bx & 0xFF;
|
|
|
|
ret >>= 8;
|
|
return ret & 0xFF;
|
|
}
|
|
|
|
|
|
static int bios_read_config_byte(const pci_location_t *state, uint32_t reg, uint8_t *value)
|
|
{
|
|
uint32_t bx, ret;
|
|
|
|
bx = state->bus;
|
|
bx <<= 8;
|
|
bx |= state->dev_fn;
|
|
__asm__(
|
|
"lcall *(%%esi) \n\t"
|
|
"jc 1f \n\t"
|
|
"xor %%ah,%%ah \n"
|
|
"1:"
|
|
: "=c"(*value),
|
|
"=a"(ret)
|
|
: "1"(PCIBIOS_READ_CONFIG_BYTE),
|
|
"b"(bx),
|
|
"D"(reg),
|
|
"S"(&bios32_entry));
|
|
ret >>= 8;
|
|
return ret & 0xFF;
|
|
}
|
|
|
|
static int bios_read_config_half(const pci_location_t *state, uint32_t reg, uint16_t *value)
|
|
{
|
|
uint32_t bx, ret;
|
|
|
|
bx = state->bus;
|
|
bx <<= 8;
|
|
bx |= state->dev_fn;
|
|
__asm__(
|
|
"lcall *(%%esi) \n\t"
|
|
"jc 1f \n\t"
|
|
"xor %%ah,%%ah \n"
|
|
"1:"
|
|
: "=c"(*value),
|
|
"=a"(ret)
|
|
: "1"(PCIBIOS_READ_CONFIG_WORD),
|
|
"b"(bx),
|
|
"D"(reg),
|
|
"S"(&bios32_entry));
|
|
ret >>= 8;
|
|
return ret & 0xFF;
|
|
}
|
|
|
|
static int bios_read_config_word(const pci_location_t *state, uint32_t reg, uint32_t *value)
|
|
{
|
|
uint32_t bx, ret;
|
|
|
|
bx = state->bus;
|
|
bx <<= 8;
|
|
bx |= state->dev_fn;
|
|
__asm__(
|
|
"lcall *(%%esi) \n\t"
|
|
"jc 1f \n\t"
|
|
"xor %%ah,%%ah \n"
|
|
"1:"
|
|
: "=c"(*value),
|
|
"=a"(ret)
|
|
: "1"(PCIBIOS_READ_CONFIG_DWORD),
|
|
"b"(bx),
|
|
"D"(reg),
|
|
"S"(&bios32_entry));
|
|
ret >>= 8;
|
|
return ret & 0xFF;
|
|
}
|
|
|
|
static int bios_write_config_byte(const pci_location_t *state, uint32_t reg, uint8_t value)
|
|
{
|
|
uint32_t bx, ret;
|
|
|
|
bx = state->bus;
|
|
bx <<= 8;
|
|
bx |= state->dev_fn;
|
|
__asm__(
|
|
"lcall *(%%esi) \n\t"
|
|
"jc 1f \n\t"
|
|
"xor %%ah,%%ah \n"
|
|
"1:"
|
|
: "=a"(ret)
|
|
: "0"(PCIBIOS_WRITE_CONFIG_BYTE),
|
|
"c"(value),
|
|
"b"(bx),
|
|
"D"(reg),
|
|
"S"(&bios32_entry));
|
|
ret >>= 8;
|
|
return ret & 0xFF;
|
|
}
|
|
|
|
static int bios_write_config_half(const pci_location_t *state, uint32_t reg, uint16_t value)
|
|
{
|
|
uint32_t bx, ret;
|
|
|
|
bx = state->bus;
|
|
bx <<= 8;
|
|
bx |= state->dev_fn;
|
|
__asm__(
|
|
"lcall *(%%esi) \n\t"
|
|
"jc 1f \n\t"
|
|
"xor %%ah,%%ah \n"
|
|
"1:"
|
|
: "=a"(ret)
|
|
: "0"(PCIBIOS_WRITE_CONFIG_WORD),
|
|
"c"(value),
|
|
"b"(bx),
|
|
"D"(reg),
|
|
"S"(&bios32_entry));
|
|
ret >>= 8;
|
|
return ret & 0xFF;
|
|
}
|
|
|
|
static int bios_write_config_word(const pci_location_t *state, uint32_t reg, uint32_t value)
|
|
{
|
|
uint32_t bx, ret;
|
|
|
|
bx = state->bus;
|
|
bx <<= 8;
|
|
bx |= state->dev_fn;
|
|
__asm__(
|
|
"lcall *(%%esi) \n\t"
|
|
"jc 1f \n\t"
|
|
"xor %%ah,%%ah \n"
|
|
"1:"
|
|
: "=a"(ret)
|
|
: "0"(PCIBIOS_WRITE_CONFIG_DWORD),
|
|
"c"(value),
|
|
"b"(bx),
|
|
"D"(reg),
|
|
"S"(&bios32_entry));
|
|
ret >>= 8;
|
|
return ret & 0xFF;
|
|
}
|
|
|
|
static int bios_get_irq_routing_options(irq_routing_options_t *route_buffer, uint16_t *pciIrqs)
|
|
{
|
|
uint32_t ret;
|
|
|
|
__asm__(
|
|
"lcall *(%%esi) \n\t"
|
|
"jc 1f \n\t"
|
|
"xor %%ah,%%ah \n"
|
|
"1:"
|
|
: "=b"(*pciIrqs),
|
|
"=a"(ret)
|
|
: "1"(PCIBIOS_GET_IRQ_ROUTING_OPTIONS),
|
|
"b"(0),
|
|
"D"(route_buffer),
|
|
"S"(&bios32_entry));
|
|
ret >>= 8;
|
|
return ret & 0xff;
|
|
}
|
|
|
|
static int bios_set_irq_hw_int(const pci_location_t *state, uint8_t int_pin, uint8_t irq)
|
|
{
|
|
uint32_t bx, cx, ret;
|
|
|
|
bx = state->bus;
|
|
bx <<= 8;
|
|
bx |= state->dev_fn;
|
|
cx = irq;
|
|
cx <<= 8;
|
|
cx |= int_pin;
|
|
__asm__(
|
|
"lcall *(%%esi) \n\t"
|
|
"jc 1f \n\t"
|
|
"xor %%ah,%%ah \n"
|
|
"1:"
|
|
: "=a"(ret)
|
|
: "0"(PCIBIOS_PCI_SET_IRQ_HW_INT),
|
|
"b"(bx),
|
|
"c"(cx),
|
|
"S"(&bios32_entry));
|
|
ret >>= 8;
|
|
return ret & 0xFF;
|
|
}
|
|
|
|
static const char *pci_signature = "PCI ";
|
|
static int pci_bios_detect(void) {
|
|
pci_bios_info * pci = find_pci_bios_info();
|
|
|
|
if (pci != NULL) {
|
|
/*printf("Found PCI structure at %08x\n", (uint32_t) pci);
|
|
|
|
printf("\nPCI header info:\n");
|
|
printf("%c%c%c%c\n", pci->magic[0], pci->magic[1], pci->magic[2],
|
|
pci->magic[3]);
|
|
printf("%08x\n", (uint32_t) pci->entry);
|
|
printf("%d\n", pci->length * 16);
|
|
printf("%d\n", pci->checksum);*/
|
|
|
|
uint32_t adr, temp, len;
|
|
uint8_t err;
|
|
|
|
bios32_entry.offset = (uint32_t) pci->entry;
|
|
bios32_entry.selector = CODE_SELECTOR;
|
|
|
|
__asm__(
|
|
"lcall *(%%edi)"
|
|
: "=a"(err), /* AL out=status */
|
|
"=b"(adr), /* EBX out=code segment base adr */
|
|
"=c"(len), /* ECX out=code segment size */
|
|
"=d"(temp) /* EDX out=entry pt offset in code */
|
|
: "0"(0x49435024),/* EAX in=service="$PCI" */
|
|
"1"(0), /* EBX in=0=get service entry pt */
|
|
"D"(&bios32_entry)
|
|
);
|
|
|
|
if (err == 0x80) {
|
|
dprintf(INFO, "BIOS32 found, but no PCI BIOS\n");
|
|
return -1;
|
|
}
|
|
|
|
if (err != 0) {
|
|
dprintf(INFO, "BIOS32 call to locate PCI BIOS returned %x\n", err);
|
|
return -1;
|
|
}
|
|
|
|
bios32_entry.offset = adr + temp;
|
|
|
|
// now call PCI_BIOS_PRESENT to get version, hw mechanism, and last bus
|
|
uint16_t present, version, busses;
|
|
uint32_t signature;
|
|
__asm__(
|
|
"lcall *(%%edi) \n\t"
|
|
"jc 1f \n\t"
|
|
"xor %%ah,%%ah \n"
|
|
"1:"
|
|
: "=a"(present),
|
|
"=b"(version),
|
|
"=c"(busses),
|
|
"=d"(signature)
|
|
: "0"(PCIBIOS_PRESENT),
|
|
"D"(&bios32_entry)
|
|
);
|
|
|
|
if (present & 0xff00) {
|
|
dprintf(INFO, "PCI_BIOS_PRESENT call returned ah=%02x\n", present >> 8);
|
|
return -1;
|
|
}
|
|
|
|
if (signature != *(uint32_t *)pci_signature) {
|
|
dprintf(INFO, "PCI_BIOS_PRESENT call returned edx=%08x\n", signature);
|
|
return -1;
|
|
}
|
|
|
|
//dprintf(DEBUG, "busses=%04x\n", busses);
|
|
last_bus = busses & 0xff;
|
|
|
|
g_pci_find_pci_device = bios_find_pci_device;
|
|
g_pci_find_pci_class_code = bios_find_pci_class_code;
|
|
|
|
g_pci_read_config_word = bios_read_config_word;
|
|
g_pci_read_config_half = bios_read_config_half;
|
|
g_pci_read_config_byte = bios_read_config_byte;
|
|
|
|
g_pci_write_config_word = bios_write_config_word;
|
|
g_pci_write_config_half = bios_write_config_half;
|
|
g_pci_write_config_byte = bios_write_config_byte;
|
|
|
|
g_pci_get_irq_routing_options = bios_get_irq_routing_options;
|
|
g_pci_set_irq_hw_int = bios_set_irq_hw_int;
|
|
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|