aboutsummaryrefslogtreecommitdiffstats
path: root/tools/firmware/hvmloader/pci.c
blob: 44168e2929f9c5c85641e480f1a735033e05a434 (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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
/*
 * pci.c: HVM PCI setup.
 *
 * Leendert van Doorn, leendert@watson.ibm.com
 * Copyright (c) 2005, International Business Machines Corporation.
 *
 * Copyright (c) 2006, Keir Fraser, XenSource Inc.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307 USA.
 */

#include "util.h"
#include "hypercall.h"
#include "config.h"
#include "pci_regs.h"

#include <xen/memory.h>
#include <xen/hvm/ioreq.h>

unsigned long pci_mem_start = PCI_MEM_START;
unsigned long pci_mem_end = PCI_MEM_END;

enum virtual_vga virtual_vga = VGA_none;
unsigned long igd_opregion_pgbase = 0;

void pci_setup(void)
{
    uint8_t is_64bar, using_64bar, bar64_relocate = 0;
    uint32_t devfn, bar_reg, cmd, bar_data, bar_data_upper;
    uint64_t base, bar_sz, bar_sz_upper, mmio_total = 0;
    uint32_t vga_devfn = 256;
    uint16_t class, vendor_id, device_id;
    unsigned int bar, pin, link, isa_irq;
    int64_t mmio_left;

    /* Resources assignable to PCI devices via BARs. */
    struct resource {
        uint64_t base, max;
    } *resource, mem_resource, high_mem_resource, io_resource;

    /* Create a list of device BARs in descending order of size. */
    struct bars {
        uint32_t is_64bar;
        uint32_t devfn;
        uint32_t bar_reg;
        uint64_t bar_sz;
    } *bars = (struct bars *)scratch_start;
    unsigned int i, nr_bars = 0;

    /* Program PCI-ISA bridge with appropriate link routes. */
    isa_irq = 0;
    for ( link = 0; link < 4; link++ )
    {
        do { isa_irq = (isa_irq + 1) & 15;
        } while ( !(PCI_ISA_IRQ_MASK & (1U << isa_irq)) );
        pci_writeb(PCI_ISA_DEVFN, 0x60 + link, isa_irq);
        printf("PCI-ISA link %u routed to IRQ%u\n", link, isa_irq);
    }

    /* Program ELCR to match PCI-wired IRQs. */
    outb(0x4d0, (uint8_t)(PCI_ISA_IRQ_MASK >> 0));
    outb(0x4d1, (uint8_t)(PCI_ISA_IRQ_MASK >> 8));

    /* Scan the PCI bus and map resources. */
    for ( devfn = 0; devfn < 256; devfn++ )
    {
        class     = pci_readw(devfn, PCI_CLASS_DEVICE);
        vendor_id = pci_readw(devfn, PCI_VENDOR_ID);
        device_id = pci_readw(devfn, PCI_DEVICE_ID);
        if ( (vendor_id == 0xffff) && (device_id == 0xffff) )
            continue;

        ASSERT((devfn != PCI_ISA_DEVFN) ||
               ((vendor_id == 0x8086) && (device_id == 0x7000)));

        switch ( class )
        {
        case 0x0300:
            /* If emulated VGA is found, preserve it as primary VGA. */
            if ( (vendor_id == 0x1234) && (device_id == 0x1111) )
            {
                vga_devfn = devfn;
                virtual_vga = VGA_std;
            }
            else if ( (vendor_id == 0x1013) && (device_id == 0xb8) )
            {
                vga_devfn = devfn;
                virtual_vga = VGA_cirrus;
            }
            else if ( virtual_vga == VGA_none )
            {
                vga_devfn = devfn;
                virtual_vga = VGA_pt;
                if ( vendor_id == 0x8086 )
                {
                    igd_opregion_pgbase = mem_hole_alloc(IGD_OPREGION_PAGES);
                    /*
                     * Write the the OpRegion offset to give the opregion
                     * address to the device model. The device model will trap 
                     * and map the OpRegion at the give address.
                     */
                    pci_writel(vga_devfn, PCI_INTEL_OPREGION,
                               igd_opregion_pgbase << PAGE_SHIFT);
                }
            }
            break;
        case 0x0680:
            /* PIIX4 ACPI PM. Special device with special PCI config space. */
            ASSERT((vendor_id == 0x8086) && (device_id == 0x7113));
            pci_writew(devfn, 0x20, 0x0000); /* No smb bus IO enable */
            pci_writew(devfn, 0xd2, 0x0000); /* No smb bus IO enable */
            pci_writew(devfn, 0x22, 0x0000);
            pci_writew(devfn, 0x3c, 0x0009); /* Hardcoded IRQ9 */
            pci_writew(devfn, 0x3d, 0x0001);
            pci_writel(devfn, 0x40, ACPI_PM1A_EVT_BLK_ADDRESS_V1 | 1);
            pci_writeb(devfn, 0x80, 0x01); /* enable PM io space */
            break;
        case 0x0101:
            if ( vendor_id == 0x8086 )
            {
                /* Intel ICHs since PIIX3: enable IDE legacy mode. */
                pci_writew(devfn, 0x40, 0x8000); /* enable IDE0 */
                pci_writew(devfn, 0x42, 0x8000); /* enable IDE1 */
            }
            break;
        }

        /* Map the I/O memory and port resources. */
        for ( bar = 0; bar < 7; bar++ )
        {
            bar_sz_upper = 0;
            bar_reg = PCI_BASE_ADDRESS_0 + 4*bar;
            if ( bar == 6 )
                bar_reg = PCI_ROM_ADDRESS;

            bar_data = pci_readl(devfn, bar_reg);
            is_64bar = !!((bar_data & (PCI_BASE_ADDRESS_SPACE |
                         PCI_BASE_ADDRESS_MEM_TYPE_MASK)) ==
                         (PCI_BASE_ADDRESS_SPACE_MEMORY |
                         PCI_BASE_ADDRESS_MEM_TYPE_64));
            pci_writel(devfn, bar_reg, ~0);
            bar_sz = pci_readl(devfn, bar_reg);
            pci_writel(devfn, bar_reg, bar_data);

            bar_sz &= (((bar_data & PCI_BASE_ADDRESS_SPACE) ==
                        PCI_BASE_ADDRESS_SPACE_MEMORY) ?
                       PCI_BASE_ADDRESS_MEM_MASK :
                       (PCI_BASE_ADDRESS_IO_MASK & 0xffff));
            if (is_64bar) {
                bar_data_upper = pci_readl(devfn, bar_reg + 4);
                pci_writel(devfn, bar_reg + 4, ~0);
                bar_sz_upper = pci_readl(devfn, bar_reg + 4);
                pci_writel(devfn, bar_reg + 4, bar_data_upper);
                bar_sz = (bar_sz_upper << 32) | bar_sz;
            }
            bar_sz &= ~(bar_sz - 1);
            if ( bar_sz == 0 )
                continue;

            for ( i = 0; i < nr_bars; i++ )
                if ( bars[i].bar_sz < bar_sz )
                    break;

            if ( i != nr_bars )
                memmove(&bars[i+1], &bars[i], (nr_bars-i) * sizeof(*bars));

            bars[i].is_64bar = is_64bar;
            bars[i].devfn   = devfn;
            bars[i].bar_reg = bar_reg;
            bars[i].bar_sz  = bar_sz;

            if ( (bar_data & PCI_BASE_ADDRESS_SPACE) ==
                 PCI_BASE_ADDRESS_SPACE_MEMORY )
                mmio_total += bar_sz;

            nr_bars++;

            /*The upper half is already calculated, skip it! */
            if (is_64bar)
                bar++;
        }

        /* Map the interrupt. */
        pin = pci_readb(devfn, PCI_INTERRUPT_PIN);
        if ( pin != 0 )
        {
            /* This is the barber's pole mapping used by Xen. */
            link = ((pin - 1) + (devfn >> 3)) & 3;
            isa_irq = pci_readb(PCI_ISA_DEVFN, 0x60 + link);
            pci_writeb(devfn, PCI_INTERRUPT_LINE, isa_irq);
            printf("pci dev %02x:%x INT%c->IRQ%u\n",
                   devfn>>3, devfn&7, 'A'+pin-1, isa_irq);
        }

        /* Enable bus mastering. */
        cmd = pci_readw(devfn, PCI_COMMAND);
        cmd |= PCI_COMMAND_MASTER;
        pci_writew(devfn, PCI_COMMAND, cmd);
    }

    while ( (mmio_total > (pci_mem_end - pci_mem_start)) &&
            ((pci_mem_start << 1) != 0) )
        pci_mem_start <<= 1;

    if ( (pci_mem_start << 1) != 0 )
    {
        printf("Low MMIO hole not large enough for all devices,"
               " relocating some BARs to 64-bit\n");
        bar64_relocate = 1;
    }

    /* Relocate RAM that overlaps PCI space (in 64k-page chunks). */
    while ( (pci_mem_start >> PAGE_SHIFT) < hvm_info->low_mem_pgend )
    {
        struct xen_add_to_physmap xatp;
        unsigned int nr_pages = min_t(
            unsigned int,
            hvm_info->low_mem_pgend - (pci_mem_start >> PAGE_SHIFT),
            (1u << 16) - 1);
        if ( hvm_info->high_mem_pgend == 0 )
            hvm_info->high_mem_pgend = 1ull << (32 - PAGE_SHIFT);
        hvm_info->low_mem_pgend -= nr_pages;
        printf("Relocating 0x%x pages from "PRIllx" to "PRIllx\
               " for lowmem MMIO hole\n",
               nr_pages,
               PRIllx_arg(((uint64_t)hvm_info->low_mem_pgend)<<PAGE_SHIFT),
               PRIllx_arg(((uint64_t)hvm_info->high_mem_pgend)<<PAGE_SHIFT));
        xatp.domid = DOMID_SELF;
        xatp.space = XENMAPSPACE_gmfn_range;
        xatp.idx   = hvm_info->low_mem_pgend;
        xatp.gpfn  = hvm_info->high_mem_pgend;
        xatp.size  = nr_pages;
        if ( hypercall_memory_op(XENMEM_add_to_physmap, &xatp) != 0 )
            BUG();
        hvm_info->high_mem_pgend += nr_pages;
    }

    high_mem_resource.base = ((uint64_t)hvm_info->high_mem_pgend) << PAGE_SHIFT; 
    high_mem_resource.max = 1ull << cpu_phys_addr();
    mem_resource.base = pci_mem_start;
    mem_resource.max = pci_mem_end;
    io_resource.base = 0xc000;
    io_resource.max = 0x10000;

    mmio_left = pci_mem_end - pci_mem_start;

    /* Assign iomem and ioport resources in descending order of size. */
    for ( i = 0; i < nr_bars; i++ )
    {
        devfn   = bars[i].devfn;
        bar_reg = bars[i].bar_reg;
        bar_sz  = bars[i].bar_sz;

        using_64bar = bars[i].is_64bar && bar64_relocate && (mmio_left < bar_sz);
        bar_data = pci_readl(devfn, bar_reg);

        if ( (bar_data & PCI_BASE_ADDRESS_SPACE) ==
             PCI_BASE_ADDRESS_SPACE_MEMORY )
        {
            /* Mapping high memory if PCI deivce is 64 bits bar and the bar size
               is larger than 512M */
            if (using_64bar && (bar_sz > PCI_MIN_BIG_BAR_SIZE)) {
                if ( high_mem_resource.base & (bar_sz - 1) )
                    high_mem_resource.base = high_mem_resource.base - 
                        (high_mem_resource.base & (bar_sz - 1)) + bar_sz;
                else
                    high_mem_resource.base = high_mem_resource.base - 
                        (high_mem_resource.base & (bar_sz - 1));
                resource = &high_mem_resource;
                bar_data &= ~PCI_BASE_ADDRESS_MEM_MASK;
            } 
            else {
                resource = &mem_resource;
                bar_data &= ~PCI_BASE_ADDRESS_MEM_MASK;
            }
            mmio_left -= bar_sz;
        }
        else
        {
            resource = &io_resource;
            bar_data &= ~PCI_BASE_ADDRESS_IO_MASK;
        }

        base = (resource->base  + bar_sz - 1) & ~(uint64_t)(bar_sz - 1);
        bar_data |= (uint32_t)base;
        bar_data_upper = (uint32_t)(base >> 32);
        base += bar_sz;

        if ( (base < resource->base) || (base > resource->max) )
        {
            printf("pci dev %02x:%x bar %02x size "PRIllx": no space for "
                   "resource!\n", devfn>>3, devfn&7, bar_reg,
                   PRIllx_arg(bar_sz));
            continue;
        }

        resource->base = base;

        pci_writel(devfn, bar_reg, bar_data);
        if (using_64bar)
            pci_writel(devfn, bar_reg + 4, bar_data_upper);
        printf("pci dev %02x:%x bar %02x size "PRIllx": %x%08x\n",
               devfn>>3, devfn&7, bar_reg,
               PRIllx_arg(bar_sz),
               bar_data_upper, bar_data);
			

        /* Now enable the memory or I/O mapping. */
        cmd = pci_readw(devfn, PCI_COMMAND);
        if ( (bar_reg == PCI_ROM_ADDRESS) ||
             ((bar_data & PCI_BASE_ADDRESS_SPACE) ==
              PCI_BASE_ADDRESS_SPACE_MEMORY) )
            cmd |= PCI_COMMAND_MEMORY;
        else
            cmd |= PCI_COMMAND_IO;
        pci_writew(devfn, PCI_COMMAND, cmd);
    }

    if ( vga_devfn != 256 )
    {
        /*
         * VGA registers live in I/O space so ensure that primary VGA
         * has IO enabled, even if there is no I/O BAR on that
         * particular device.
         */
        cmd = pci_readw(vga_devfn, PCI_COMMAND);
        cmd |= PCI_COMMAND_IO;
        pci_writew(vga_devfn, PCI_COMMAND, cmd);
    }
}

/*
 * Local variables:
 * mode: C
 * c-file-style: "BSD"
 * c-basic-offset: 4
 * tab-width: 4
 * indent-tabs-mode: nil
 * End:
 */