aboutsummaryrefslogtreecommitdiff
path: root/mubios/drivers/pci.c
blob: b9c955c2c71d61cd66550661784805c72e5a5ddc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/*
 RCC-Project

 Copyright (C) 2026 Richard Qin

 This file is part of RCC-Project.

 RCC-Project is free software; you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free
 Software Foundation; either version 3 of the License, or (at your option) any
 later version.

 RCC-Project is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 RCC-Project; see the file COPYING.  If not see
 <http://www.gnu.org/licenses/>.
 */

#include "bios.h"
#include "io.h"

#define PCI_CONFIG_ADDRESS 0x0CF8u
#define PCI_CONFIG_DATA 0x0CFCu
#define PCI_VENDOR_DEVICE 0x00u
#define PCI_INTERRUPT_LINE 0x3Cu
#define PCI_INTERRUPT_PIN 0x3Du
#define PCI_VENDOR_ABSENT 0xFFFFu
#define PIIX3_ISA_ID 0x70008086u
#define PIIX4_PM_ID 0x71138086u
#define PIIX_PIRQ_ROUTE 0x60u
#define PIIX_SCI_IRQ 9u
#define ELCR_MASTER 0x04D0u
#define ELCR_SLAVE 0x04D1u

/* Match SeaBIOS's PIRQ A-D assignment for the QEMU pc machine. */
static const u8 pirq_irqs[4] = { 10u, 10u, 11u, 11u };

static u32
pci_read (u16 devfn, u8 offset)
{
  io_out32 (PCI_CONFIG_ADDRESS,
            0x80000000u | ((u32)devfn << 8) | (offset & 0xFCu));
  return io_in32 (PCI_CONFIG_DATA);
}

static void
pci_write8 (u16 devfn, u8 offset, u8 value)
{
  io_out32 (PCI_CONFIG_ADDRESS,
            0x80000000u | ((u32)devfn << 8) | (offset & 0xFCu));
  io_out8 ((u16)(PCI_CONFIG_DATA + (offset & 3u)), value);
}

void
pci_irq_init (void)
{
  u16 bridge;
  u16 devfn;
  u16 level_irqs = 0;
  u32 id;
  u8 pin;
  u8 pirq;
  u8 irq;

  /* PIRQ routing registers below belong to the PIIX3 ISA bridge. */
  for (bridge = 0; bridge < 256u; ++bridge)
    if (pci_read (bridge, PCI_VENDOR_DEVICE) == PIIX3_ISA_ID)
      break;

  if (bridge == 256u)
    return;

  for (pirq = 0; pirq < 4u; ++pirq)
    {
      irq = pirq_irqs[pirq];
      pci_write8 (bridge, (u8)(PIIX_PIRQ_ROUTE + pirq), irq);
      level_irqs |= (u16)(1u << irq);
    }

  for (devfn = 0; devfn < 256u; ++devfn)
    {
      id = pci_read (devfn, PCI_VENDOR_DEVICE);
      /* PCI returns an all-ones vendor ID for an absent function. */
      if ((u16)id == PCI_VENDOR_ABSENT)
        continue;

      pin = (u8)(pci_read (devfn, PCI_INTERRUPT_PIN) >> 8);
      /* Interrupt Pin zero denotes a function without a PCI INTx pin. */
      if (pin == 0u)
        continue;

      /* The PIIX PM SCI is wired directly to IRQ9, not through PIRQ. */
      if (id == PIIX4_PM_ID)
        {
          irq = PIIX_SCI_IRQ;
          level_irqs |= (u16)(1u << irq);
        }
      else
        {
          /* QEMU pc: PIRQ = (slot - 1 + zero-based INTx pin) & 3. */
          pirq = (u8)(((devfn >> 3) - 1u + pin - 1u) & 3u);
          irq = pirq_irqs[pirq];
        }

      pci_write8 (devfn, PCI_INTERRUPT_LINE, irq);
    }

  /* PCI INTx and SCI are level-triggered; preserve other IRQ settings. */
  io_out8 (ELCR_MASTER, (u8)(io_in8 (ELCR_MASTER) | (u8)level_irqs));
  io_out8 (ELCR_SLAVE, (u8)(io_in8 (ELCR_SLAVE) | (level_irqs >> 8)));
}