summaryrefslogtreecommitdiff
path: root/i386/i386/smp.c
blob: 05e9de678a5fb3d8221bf859c96cde13fc92ac6f (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
/* smp.h - i386 SMP controller for Mach
   Copyright (C) 2020 Free Software Foundation, Inc.
   Written by Almudena Garcia Jurado-Centurion

   This file is part of GNU Mach.

   GNU Mach 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, or (at your option)
   any later version.

   GNU Mach 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 this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA. */

#include <string.h>
#include <i386/apic.h>
#include <i386/smp.h>
#include <i386/cpu.h>
#include <i386/pio.h>
#include <i386/vm_param.h>
#include <i386at/idt.h>
#include <i386at/cram.h>
#include <i386at/acpi_parse_apic.h>
#include <kern/printf.h>
#include <mach/machine.h>

#include <kern/smp.h>

/*
 * smp_data_init: initialize smp_data structure
 * Must be called after smp_init(), once all APIC structures
 * has been initialized
 */
static void smp_data_init(void)
{
    uint8_t numcpus = apic_get_numcpus();
    smp_set_numcpus(numcpus);

    for(int i = 0; i < numcpus; i++){
            machine_slot[i].is_cpu = TRUE;
    }

}

static void smp_send_ipi(unsigned apic_id, unsigned vector)
{
    unsigned long flags;

    cpu_intr_save(&flags);

    apic_send_ipi(NO_SHORTHAND, FIXED, PHYSICAL, ASSERT, EDGE, vector, apic_id);

    do {
        cpu_pause();
    } while(lapic->icr_low.delivery_status == SEND_PENDING);

    apic_send_ipi(NO_SHORTHAND, FIXED, PHYSICAL, DE_ASSERT, EDGE, vector, apic_id);

    do {
        cpu_pause();
    } while(lapic->icr_low.delivery_status == SEND_PENDING);

    cpu_intr_restore(flags);
}

void smp_remote_ast(unsigned apic_id)
{
    smp_send_ipi(apic_id, CALL_AST_CHECK);
}

void smp_pmap_update(unsigned apic_id)
{
    smp_send_ipi(apic_id, CALL_PMAP_UPDATE);
}

static void
wait_for_ipi(void)
{
    /* This could have a timeout, but if the IPI
     * is never delivered, its a disaster anyway */
    while (lapic->icr_low.delivery_status == SEND_PENDING) {
	cpu_pause();
    }
}

static int
smp_send_ipi_init(int apic_id)
{
    int err;

    lapic->error_status.r = 0;

    /* Assert INIT IPI:
     *
     * This is EDGE triggered to match the deassert
     */
    apic_send_ipi(NO_SHORTHAND, INIT, PHYSICAL, ASSERT, EDGE, 0, apic_id);

    /* Wait for delivery */
    wait_for_ipi();
    hpet_mdelay(10);

    /* Deassert INIT IPI:
     *
     * NB: This must be an EDGE triggered deassert signal.
     * A LEVEL triggered deassert is only supported on very old hardware
     * that does not support STARTUP IPIs at all, and instead jump
     * via a warm reset vector.
     */
    apic_send_ipi(NO_SHORTHAND, INIT, PHYSICAL, DE_ASSERT, EDGE, 0, apic_id);

    /* Wait for delivery */
    wait_for_ipi();

    err = lapic->error_status.r;
    if (err) {
        printf("ESR error upon INIT 0x%x\n", err);
    }
    return 0;
}

static int
smp_send_ipi_startup(int apic_id, int vector)
{
    int err;

    lapic->error_status.r = 0;

    /* StartUp IPI:
     *
     * Have not seen any documentation for trigger mode for this IPI
     * but it seems to work with EDGE.  (AMD BKDG FAM16h document specifies dont care)
     */
    apic_send_ipi(NO_SHORTHAND, STARTUP, PHYSICAL, ASSERT, EDGE, vector, apic_id);

    /* Wait for delivery */
    wait_for_ipi();

    err = lapic->error_status.r;
    if (err) {
        printf("ESR error upon STARTUP 0x%x\n", err);
    }
    return 0;
}

/* See Intel IA32/64 Software Developer's Manual 3A Section 8.4.4.1 */
int smp_startup_cpu(unsigned apic_id, phys_addr_t start_eip)
{
#if 0
    /* This block goes with a legacy method of INIT that only works with
     * old hardware that does not support SIPIs.
     * Must use INIT DEASSERT LEVEL triggered IPI to use this block.
     * (At least one AMD FCH does not support this IPI mode,
     * See AMD BKDG FAM16h document # 48751 page 461).
     */

    /* Tell CMOS to warm reset through through 40:67 */
    outb(CMOS_ADDR, CMOS_SHUTDOWN);
    outb(CMOS_DATA, CM_JMP_467);

    /* Set warm reset vector to point to AP startup code */
    uint16_t dword[2];
    dword[0] = 0;
    dword[1] = start_eip >> 4;
    memcpy((uint8_t *)phystokv(0x467), dword, 4);
#endif

    /* Local cache flush */
    asm("wbinvd":::"memory");

    printf("Sending IPIs to APIC ID %u...\n", apic_id);

    smp_send_ipi_init(apic_id);
    hpet_mdelay(10);
    smp_send_ipi_startup(apic_id, start_eip >> STARTUP_VECTOR_SHIFT);
    hpet_udelay(200);
    smp_send_ipi_startup(apic_id, start_eip >> STARTUP_VECTOR_SHIFT);
    hpet_udelay(200);

    printf("done\n");
    return 0;
}

/*
 * smp_init: initialize the SMP support, starting the cpus searching
 * and enumeration.
 */
int smp_init(void)
{
    smp_data_init();

    return 0;
}