summaryrefslogtreecommitdiff
path: root/pci.c
blob: 351e5aaa7d692e278b3fd14485503b7465dcc8b4 (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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/* Simplistic PCI support.

   Copyright (C) 2011 Richard Henderson

   This file is part of QEMU PALcode.

   This program 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 2 of the License or
   (at your option) any later version.

   This program 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 text
   of the GNU General Public License for more details.

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

/* We don't bother supporting PCI bridges, because the device model we're
   currently using for QEMU doesn't build any.

   We don't bother to build real datastructures in memory, because it's
   fairly quick under emulation simply to access configuration space again.
   This helps when running kernels under the emulator that might have
   re-organized the BARs out from under us.  */

#include "protos.h"
#include "pci.h"
#include "pci_regs.h"
#include SYSTEM_H


#define PCI_SLOT_MAX		32
#define PCI_FUNC_MAX		8
#define PCI_REGION_ROM		6
#define PCI_REGIONS_MAX		7


void
pci_config_maskw(int bdf, int addr, uint16_t off, uint16_t on)
{
  uint16_t val = pci_config_readw(bdf, addr);
  val = (val & ~off) | on;
  pci_config_writew(bdf, addr, val);
}

int
pci_next(int bdf, int *pmax)
{
  int max;

  if (PCI_FUNC(bdf) == 1)
    {
      /* If the last device was not a multi-function device, skip to next.  */
      if ((pci_config_readb(bdf-1, PCI_HEADER_TYPE) & 0x80) == 0)
        bdf += 7;
    }

  max = *pmax;
  while (1)
    {
      uint16_t vendor;

      /* ??? Support multiple PCI busses here at some point.  */
      if (bdf >= max)
	return -1;

      /* Check if there is a device present at the location.  */
      vendor = pci_config_readw(bdf, PCI_VENDOR_ID);
      if (vendor != 0x0000 && vendor != 0xffff)
	return bdf;

      bdf += (PCI_FUNC(bdf) == 0 ? 8 : 1);
    }
}

static void
pci_setup_device(int bdf, uint32_t *p_io_base, uint32_t *p_mem_base)
{
  int vendor_id, device_id, class_id, region;

  vendor_id = pci_config_readw(bdf, PCI_VENDOR_ID);
  device_id = pci_config_readw(bdf, PCI_DEVICE_ID);
  class_id = pci_config_readw(bdf, PCI_CLASS_DEVICE);

  printf("PCI: %02x:%02x:%x class %04x id %04x:%04x\r\n",
	 PCI_BUS(bdf), PCI_SLOT(bdf), PCI_FUNC(bdf),
         class_id, vendor_id, device_id);

  for (region = 0; region < PCI_REGION_ROM; region++)
    {
      int ofs = PCI_BASE_ADDRESS_0 + region * 4;
      uint32_t old, mask, val, size, align;
      uint32_t *p_base;

      old = pci_config_readl(bdf, ofs);
      if (old & PCI_BASE_ADDRESS_SPACE_IO)
	{
	  mask = PCI_BASE_ADDRESS_IO_MASK;
	  p_base = p_io_base;
	}
      else
	{
	  mask = PCI_BASE_ADDRESS_MEM_MASK;
	  p_base = p_mem_base;
	}

      pci_config_writel(bdf, ofs, -1);
      val = pci_config_readl(bdf, ofs);
      pci_config_writel(bdf, ofs, old);

      align = size = ~(val & mask) + 1;
      if (val != 0)
	{
	  uint32_t addr = *p_base;
	  addr = (addr + align - 1) & ~(align - 1);
	  *p_base = addr + size;
	  pci_config_writel(bdf, ofs, addr);

	  printf("PCI:   region %d: %08x\r\n", region, addr);

	  if ((old & (PCI_BASE_ADDRESS_SPACE | PCI_BASE_ADDRESS_MEM_TYPE_MASK))
              == (PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_64))
	    {
	      pci_config_writel(bdf, ofs + 4, 0);
	      region++;
	    }
	}
    }

  pci_config_maskw(bdf, PCI_COMMAND, 0, PCI_COMMAND_IO | PCI_COMMAND_MEMORY);

  /* Map the interrupt and program the IRQ into the line register.
     Some operating systems rely on the Console providing this information
     in order to avoid having mapping tables for every possible system
     variation.  */

  const uint8_t pin = pci_config_readb(bdf, PCI_INTERRUPT_PIN);
  const uint8_t slot = PCI_SLOT(bdf);
  const uint8_t irq = MAP_PCI_INTERRUPT(slot, pin, class_id);

  pci_config_writeb(bdf, PCI_INTERRUPT_LINE, irq);
}

void
pci_setup(void)
{
  uint32_t io_base = 0xc000;
  uint32_t mem_base = 256 * 1024 * 1024;
  int bdf, max;

  foreachpci (bdf, max)
    pci_setup_device(bdf, &io_base, &mem_base);
}