> for any patch it’s best to not just show a single large diff but to
> split the changes into logically related commits
I've just split the patch. I enumerated them following the dependencies
order.

> The commit
> message should describe the changes in a GNU-style ChangeLog format; you
> may also add additional descriptions.

Where can I add this information? In the same patch or in a log file?

Thanks


El dom., 19 jul. 2020 a las 20:23, Almudena Garcia (<
liberamenso10...@gmail.com>) escribió:

> Anyway, my patch is short. Maybe I can split It manually, taking care
> about dependencies between blocks.
>
> El dom., 19 jul. 2020 a las 20:17, Almudena Garcia (<
> liberamenso10...@gmail.com>) escribió:
>
>> Thanks for your explanation:
>>
>> > To commit only some changes and not others you can select lines of
>> > interest with “git add -p” (or similar).  Once all connected changes
>> > have been staged you can commit them.  Do this repeatedly until you have
>> > a series of commits that are all small enough that a reviewer can
>> > understand them (and thus your thinking) at a glance.
>>
>> I have already a commit list pushed in my GitHub repository. You can see
>> It here: https://github.com/AlmuHS/GNUMach_SMP/commits/smp-new
>> But, in this case, my code is almost written from scratch, so It's
>> complex to filter line by line.
>> The code only makes sense in a single piece. Otherway, the code doesn't
>> compile or does nothing.
>>
>> > You can then turn that series of commits into a series of patches with
>> > “git format-patch”.  For example, “git format-patch -10” will generate
>> > 10 patch files from the last 10 commits.
>>
>> Ok, I'll try this. But there are so many commits.
>>
>>
>> El dom., 19 jul. 2020 a las 19:52, Ricardo Wurmus (<rek...@elephly.net>)
>> escribió:
>>
>>>
>>> Hi,
>>>
>>> for any patch it’s best to not just show a single large diff but to
>>> split the changes into logically related commits.  You’re probably
>>> working with Git, so the unit that we’re working with is a Git commit.
>>>
>>> You should group related changes and commit them together.  The commit
>>> message should describe the changes in a GNU-style ChangeLog format; you
>>> may also add additional descriptions.  Here’s an example:
>>>
>>> --8<---------------cut here---------------start------------->8---
>>> kern: Frobnicate the jabberwocky.
>>>
>>> In order to frobnicate the jabberwocky without confusion we only add the
>>> core functionality here.
>>>
>>> * kern/smp.c, kern/smp.h: New files.
>>> * Makefrag.am (libkernel_a_SOURCES): Add them.
>>> --8<---------------cut here---------------end--------------->8---
>>>
>>> To commit only some changes and not others you can select lines of
>>> interest with “git add -p” (or similar).  Once all connected changes
>>> have been staged you can commit them.  Do this repeatedly until you have
>>> a series of commits that are all small enough that a reviewer can
>>> understand them (and thus your thinking) at a glance.
>>>
>>> You can then turn that series of commits into a series of patches with
>>> “git format-patch”.  For example, “git format-patch -10” will generate
>>> 10 patch files from the last 10 commits.  You can attach these patches
>>> to an email, or if you have configured “git send-email” correctly you
>>> could send them directly via email to this list.  A reviewer can then
>>> comment on each commit individually and apply them one by one if they
>>> pass muster.
>>>
>>> (This process is similar for most GNU packages.)
>>>
>>> Hope this helps!
>>>
>>> --
>>> Ricardo
>>>
>>
diff --git a/i386/i386at/acpi_parse_apic.c b/i386/i386at/acpi_parse_apic.c
new file mode 100644
index 00000000..b10aa79c
--- /dev/null
+++ b/i386/i386at/acpi_parse_apic.c
@@ -0,0 +1,624 @@
+/*Copyright 2018 Juan Bosco Garcia
+ *Copyright 2018 2019 2020 Almudena Garcia Jurado-Centurion
+ *This file is part of Min_SMP.
+ *Min_SMP 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.
+ *Min_SMP 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 Min_SMP.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <i386at/acpi_parse_apic.h>
+#include <string.h> //memcmp, memcpy...
+
+#include <i386/apic.h> //lapic, ioapic...
+#include <kern/printf.h> //printf
+#include <include/stdint.h> //uint16_t, uint32_t...
+#include <mach/machine.h> //machine_slot
+#include <i386/vm_param.h> //phystokv
+#include <kern/debug.h>
+#include <vm/vm_kern.h>
+
+#define BAD_CHECKSUM -1
+#define NO_RSDP -2
+#define NO_RSDT -2
+#define BAD_SIGNATURE -3
+#define NO_APIC -4
+
+struct acpi_apic *apic_madt = NULL;
+
+static struct acpi_rsdp* acpi_get_rsdp(void);
+static int acpi_check_rsdt(struct acpi_rsdt *);
+static struct acpi_rsdt* acpi_get_rsdt(struct acpi_rsdp *rsdp, int* acpi_rsdt_n);
+static struct acpi_apic* acpi_get_apic(struct acpi_rsdt *rsdt, int acpi_rsdt_n);
+static int acpi_apic_setup(struct acpi_apic *apic);
+static int acpi_apic_add_ioapic(struct acpi_apic_ioapic *ioapic_entry);
+static int acpi_apic_add_lapic(struct acpi_apic_lapic *lapic_entry);
+static int acpi_apic_parse_table(struct acpi_apic *apic);
+
+
+/* acpi_apic_init: find the MADT/APIC table in ACPI tables
+ *  and parses It to find Local APIC and IOAPIC structures
+ *  Each Local APIC stores the info and control structores for a cpu
+ *
+ * Returns 0 if success, -1 if error
+ */
+
+int
+acpi_apic_init(void)
+{
+    struct acpi_rsdp *rsdp = 0;
+    struct acpi_rsdt *rsdt = 0;
+    int acpi_rsdt_n;
+    int ret_acpi_setup;
+
+
+    /* Try to get rsdp pointer */
+    rsdp = acpi_get_rsdp();
+    if(rsdp == NULL)
+        {
+            return NO_RSDP;
+        }
+
+    printf("rsdp address %x\n", rsdp);
+
+    /* Try to get rsdt pointer */
+    rsdt = acpi_get_rsdt(rsdp, &acpi_rsdt_n);
+    if(rsdt == NULL)
+        {
+            return NO_RSDT;
+        }
+
+    printf("rsdt address %x\n", rsdt);
+
+    apic_madt = acpi_get_apic(rsdt, acpi_rsdt_n);
+    if(apic_madt == NULL)
+        {
+            return NO_APIC;
+        }
+
+    printf("apic address %x\n", apic_madt);
+
+    acpi_print_info(rsdp, rsdt, acpi_rsdt_n);
+
+    ret_acpi_setup = acpi_apic_setup(apic_madt);
+
+    if(ret_acpi_setup != 0)
+        {
+            return ret_acpi_setup;
+        }
+
+    apic_print_info();
+
+    printf("apic setup finished\n");
+
+    return 0;
+}
+
+/* acpi_print_info: shows by screen the ACPI's rsdp and rsdt virtual address
+ *    and the number of entries stored in RSDT table
+ *
+ * Receives as input the references of RSDP and RSDT tables,
+ *    and the number of entries stored in RSDT
+ */
+
+void
+acpi_print_info(struct acpi_rsdp *rsdp, struct acpi_rsdt *rsdt, int acpi_rsdt_n)
+{
+
+    printf("ACPI:\n");
+    printf(" rsdp = %x; rsdp->rsdt_addr = %x\n", rsdp, rsdp->rsdt_addr);
+    printf(" rsdt = %x; rsdt->length = %x (n = %x)\n", rsdt, rsdt->header.length,
+           acpi_rsdt_n);
+}
+
+/* acpi_checksum: calculates the checksum of an ACPI table
+ * Receives as input the virtual address of the table
+ *
+ * Returns 0 if success, other value if error
+ */
+static int
+acpi_checksum(void *addr, uint32_t length)
+{
+    char *bytes = addr;
+    int checksum = 0;
+    unsigned int i;
+
+    /* Sum all bytes of addr */
+    for(i = 0; i < length; i++)
+        {
+            checksum += bytes[i];
+        }
+
+    return checksum & 0xff;
+}
+
+/* acpi_check_rsdp:
+ * check if the RDST "candidate" table is the real RSDT table
+ *
+ * Compare the table signature with the ACPI signature for this table
+ *   and check is the checksum is correct
+ *
+ * Receives as input the reference of RSDT table
+ *
+ * Returns 0 if correct
+ */
+
+static int
+acpi_check_rsdp(struct acpi_rsdp *rsdp)
+{
+    uint32_t checksum;
+    int is_rsdp;
+    int ret_value = 0;
+
+    /* Check the integrity of RSDP */
+    if(rsdp == NULL)
+        {
+            ret_value = NO_RSDP;
+        }
+    else
+        {
+            /* Check is rsdp signature is equals to ACPI RSDP signature */
+            is_rsdp = memcmp(rsdp->signature, ACPI_RSDP_SIG, sizeof(rsdp->signature));
+
+            if(is_rsdp == 0)
+                {
+                    /* If yes, calculates rdsp checksum and check It */
+                    checksum = acpi_checksum(rsdp, sizeof(struct acpi_rsdp));
+
+                    if(checksum != 0)
+                        {
+                            ret_value = BAD_CHECKSUM;
+                        }
+                }
+            else
+                {
+                    ret_value = BAD_SIGNATURE;
+                }
+        }
+
+    return ret_value;
+}
+
+/* acpi_search_rsdp: search the rsdp table in a memory range
+ *
+ * Receives as input the initial virtual address, and the lenght
+ *   of memory range
+ *
+ * Returns the reference to rsdp structure if success, NULL if failure
+ */
+
+static struct acpi_rsdp*
+acpi_search_rsdp(void *addr, uint32_t length)
+{
+    struct acpi_rsdp *rsdp = NULL;
+
+    void *end;
+    /* check alignment */
+    if((uint32_t)addr & (ACPI_RSDP_ALIGN-1))
+        return NULL;
+
+    /* Search RDSP in memory space between addr and addr+lenght */
+    for(end = addr+length; addr < end; addr += ACPI_RSDP_ALIGN)
+        {
+
+            //Check if the current memory block store the RDSP
+            if(acpi_check_rsdp(addr) == 0)
+                {
+                    /* If yes, store RSDP address */
+                    rsdp = (struct acpi_rsdp*) addr;
+                    break;
+                }
+        }
+
+    return rsdp;
+}
+
+/* acpi_get_rsdp: tries to find the RSDP table,
+ *    searching It in many memory ranges, following ACPI docs
+ *
+ *  Returns the reference to RDSP structure if success, NULL if failure
+ */
+
+struct acpi_rsdp*
+acpi_get_rsdp(void)
+{
+    struct acpi_rsdp *rsdp = NULL;
+    uint16_t *start = 0x0;
+    uint32_t base = 0x0;
+
+    /* EDBA start address */
+    start = (uint16_t*) phystokv(0x040e);
+    base = *start;
+
+    if(base != 0)   /* Memory check */
+        {
+
+            base <<= 4; //base = base * 16
+
+            /* Search RSDP in first 1024 bytes from EDBA */
+            rsdp = acpi_search_rsdp((void*)base,1024);
+        }
+
+    if(rsdp == NULL)
+            {
+                /* If RSDP isn't in EDBA, search in the BIOS read-only memory space between 0E0000h and 0FFFFFh */
+                rsdp = acpi_search_rsdp((void*) 0x0e0000, 0x100000 - 0x0e0000);
+            }
+
+    return rsdp;
+}
+
+/* acpi_check_rsdt: check if the RSDT initial address is correct
+ *   checking its checksum
+ *
+ * Receives as input a reference for the RSDT "candidate" table
+ * Returns 0 if success
+ */
+
+static int
+acpi_check_rsdt(struct acpi_rsdt *rsdt)
+{
+    uint32_t checksum;
+    int ret_value = 0;
+
+    if(rsdt == NULL)
+        {
+            ret_value = NO_RSDT;
+        }
+    else
+        {
+            checksum = acpi_checksum(rsdt, rsdt->header.length);
+
+            if (checksum != 0)
+                {
+                    ret_value = BAD_CHECKSUM;
+                }
+        }
+
+    return ret_value;
+}
+
+/* acpi_get_rsdt: Get RSDT table reference from RSDP entries
+ *
+ * Receives as input a reference for RSDP table
+ *   and a reference to store the number of entries of RSDT
+ *
+ * Returns the reference to RSDT table if success, NULL if error
+ */
+
+static struct acpi_rsdt*
+acpi_get_rsdt(struct acpi_rsdp *rsdp, int* acpi_rsdt_n)
+{
+    phys_addr_t rsdt_phys;
+    struct acpi_rsdt *rsdt = NULL;
+    int acpi_check;
+    int signature_check;
+
+    if(rsdp != NULL)
+        {
+            /* Get rsdt address from rsdp */
+            rsdt_phys = rsdp->rsdt_addr;
+            rsdt = (struct acpi_rsdt*) kmem_map_aligned_table(rsdt_phys, sizeof(struct acpi_rsdt), VM_PROT_READ);
+
+            printf("found rsdt in address %x\n", rsdt);
+
+            /* Check is rsdt signature is equals to ACPI RSDT signature */
+            signature_check = memcmp(rsdt->header.signature, ACPI_RSDT_SIG,
+                                     sizeof(rsdt->header.signature));
+
+            if(signature_check == 0)
+                {
+                    /* Check if rsdt is correct */
+                    acpi_check = acpi_check_rsdt(rsdt);
+
+                    if(acpi_check == 0)
+                        {
+                            /* Calculated number of elements stored in rsdt */
+                            *acpi_rsdt_n = (rsdt->header.length - sizeof(rsdt->header))
+                                           / sizeof(rsdt->entry[0]);
+
+                        }
+                }
+
+            if (signature_check != 0 || acpi_check != 0)
+                {
+                    rsdt = NULL;
+                }
+        }
+
+    return rsdt;
+}
+
+/* acpi_get_apic: get MADT/APIC table from RSDT entries
+ *
+ * Receives as input the RSDT initial address,
+ *   and the number of entries of RSDT table
+ *
+ * Returns a reference to APIC/MADT table if success, NULL if failure
+ */
+
+static struct acpi_apic*
+acpi_get_apic(struct acpi_rsdt *rsdt, int acpi_rsdt_n)
+{
+    struct acpi_apic *apic = NULL;
+    struct acpi_dhdr *descr_header;
+    int check_signature;
+
+    int i;
+
+    if(rsdt != NULL)
+        {
+            /* Search APIC entries in rsdt array */
+            for(i = 0; i < acpi_rsdt_n; i++)
+                {
+                    descr_header = (struct acpi_dhdr*) kmem_map_aligned_table(rsdt->entry[i], sizeof(struct acpi_dhdr), VM_PROT_READ);
+
+                    /* Check if the entry contains an APIC */
+                    check_signature = memcmp(descr_header->signature, ACPI_APIC_SIG, sizeof(descr_header->signature));
+
+                    if(check_signature == 0)
+                        {
+                            /* If yes, store the entry in apic */
+                            apic = (struct acpi_apic*) kmem_map_aligned_table(rsdt->entry[i], sizeof(struct acpi_apic), VM_PROT_READ | VM_PROT_WRITE);
+
+                            printf("found apic in address %x\n", apic);
+                            break;
+                        }
+                }
+        }
+
+    return apic;
+}
+
+/* acpi_add_lapic: add a new Local APIC to cpu_to_lapic array
+ *    and increase the number of cpus
+ *
+ *  Receives as input the Local APIC entry in MADT/APIC table
+ */
+
+
+static int
+acpi_apic_add_lapic(struct acpi_apic_lapic *lapic_entry)
+{
+    int ret_value = 0;
+    int lapic_id;
+
+
+    if(lapic_entry == NULL)
+        {
+            ret_value = -1;
+        }
+    else
+        {
+            /* If cpu flag is correct */
+            if(lapic_entry->flags & 0x1)
+                {
+                    /* Enumerate CPU and add It to cpu/apic vector */
+                    lapic_id = lapic_entry->apic_id;
+
+                    apic_add_cpu(lapic_id);
+
+                    printf("new cpu found with apic id %x\n", lapic_entry->apic_id);;
+                }
+        }
+
+    return ret_value;
+}
+
+/* apic_add_ioapic: add a new IOAPIC to IOAPICS array
+ *   and increase the number of IOAPIC
+ *
+ * Receives as input the IOAPIC entry in MADT/APIC table
+ */
+
+static int
+acpi_apic_add_ioapic(struct acpi_apic_ioapic *ioapic_entry)
+{
+    int ret_value = 0;
+    struct ioapic_data io_apic;
+
+    if(ioapic_entry == NULL)
+        {
+            ret_value = -1;
+        }
+    else
+        {
+            /* Insert ioapic in ioapics array */
+            io_apic.apic_id = ioapic_entry->apic_id;
+            io_apic.addr = ioapic_entry->addr;
+            io_apic.base = ioapic_entry->base;
+
+            apic_add_ioapic(io_apic);
+
+            printf("new ioapic found with apic id %x\n", io_apic.apic_id);
+        }
+
+    return ret_value;
+}
+
+
+/* apic_add_ioapic: add a new IOAPIC to IOAPICS array
+ *   and increase the number of IOAPIC
+ *
+ * Receives as input the IOAPIC entry in MADT/APIC table
+ */
+
+static int
+acpi_apic_add_irq_override(struct acpi_apic_irq_override* irq_override)
+{
+    int ret_value = 0;
+    struct irq_override_data irq_over;
+
+
+    if(irq_override == NULL)
+        {
+            ret_value = -1;
+        }
+    else
+        {
+            /* Insert ioapic in ioapics array */
+            irq_over.bus = irq_override->bus;
+            irq_over.irq = irq_override->irq;
+            irq_over.gsi = irq_override->gsi;
+            irq_over.flags = irq_override->flags;
+
+            apic_add_irq_override(irq_over);
+        }
+
+    return ret_value;
+}
+
+
+/* apic_parse_table: parse the MADT/APIC table
+ *   Read the APIC/MADT table entry to entry,
+ *      registering the Local APIC or IOAPIC entries
+ */
+
+static int
+apic_parse_table(struct acpi_apic *apic)
+{
+    int ret_value = 0;
+    struct acpi_apic_dhdr *apic_entry = NULL;
+    uint32_t end = 0;
+
+    if(apic != NULL)
+        {
+            apic_entry = apic->entry;
+            end = (uint32_t) apic + apic->header.length;
+
+            /* Search in APIC entry */
+            while((uint32_t)apic_entry < end)
+                {
+                    struct acpi_apic_lapic *lapic_entry;
+                    struct acpi_apic_ioapic *ioapic_entry;
+                    struct acpi_apic_irq_override *irq_override_entry;
+
+                    /* Check entry type */
+                    switch(apic_entry->type)
+                        {
+
+                        /* If APIC entry is a CPU lapic */
+                        case ACPI_APIC_ENTRY_LAPIC:
+
+                            /* Store lapic */
+                            lapic_entry = (struct acpi_apic_lapic*) apic_entry;
+
+                            acpi_apic_add_lapic(lapic_entry);
+
+                            break;
+
+                        /* If APIC entry is an IOAPIC */
+                        case ACPI_APIC_ENTRY_IOAPIC:
+
+                            /* Store ioapic */
+                            ioapic_entry = (struct acpi_apic_ioapic*) apic_entry;
+
+                            acpi_apic_add_ioapic(ioapic_entry);
+
+                            break;
+
+                        /* If APIC entry is a IRQ */
+                        case ACPI_APIC_IRQ_OVERRIDE:
+                             irq_override_entry = (struct acpi_apic_irq_override*) apic_entry;
+
+                             acpi_apic_add_irq_override(irq_override_entry);
+                             break;
+
+                        }
+
+                    /* Get next APIC entry */
+                    apic_entry = (struct acpi_apic_dhdr*)((uint32_t) apic_entry
+                                                          + apic_entry->length);
+                }
+        }
+    else /* apic == NULL */
+        {
+            ret_value = -1;
+        }
+
+    return ret_value;
+}
+
+
+/* acpi_apic_setup: parses the APIC/MADT table.
+ *    to find the Local APIC and IOAPIC structures
+ *    and the common address for Local APIC
+ *
+ *  Receives as input a reference for APIC/MADT table
+ *  Returns 0 if success
+ *
+ * Fills the cpu_to_lapic and ioapics array, indexed by Kernel ID
+ *   with a relationship between Kernel ID and APIC ID,
+ *   and map the Local APIC common address, to fill the lapic reference
+ */
+
+static int
+acpi_apic_setup(struct acpi_apic *apic)
+{
+    int apic_checksum;
+    int ret_value = 0;
+    ApicLocalUnit* lapic;
+    int ncpus, nioapics;
+    int init_success = 0;
+
+
+    if(apic != NULL)
+        {
+
+            /* Check the checksum of the APIC */
+            apic_checksum = acpi_checksum(apic, apic->header.length);
+
+            if(apic_checksum != 0)
+                {
+                    ret_value = BAD_CHECKSUM;
+                }
+            else
+                {
+                    init_success = apic_data_init();
+                    if(init_success == 0)
+                        {
+
+                            printf("lapic found in address %x\n", apic->lapic_addr);
+
+                            /* map common lapic address */
+                            lapic = kmem_map_aligned_table(apic->lapic_addr, sizeof(ApicLocalUnit), VM_PROT_READ);
+
+                            if(lapic != NULL){
+                                printf("lapic mapped in address %x\n", lapic);
+                                apic_lapic_init(lapic);
+                            }
+
+                            apic_parse_table(apic);
+
+                            ncpus = apic_get_numcpus();
+                            nioapics = apic_get_num_ioapics();
+
+                            if(ncpus == 0 || nioapics == 0)
+                                {
+                                    ret_value = -1;
+                                }
+                            else
+                                {
+                                    apic_refit_cpulist();
+                                    printf("%d cpus found. %d ioapics found\n", ncpus, nioapics);
+                                }
+                        }
+                        else{
+                            ret_value = -1;
+                        }
+                }
+        }
+    else /* apic == NULL */
+        {
+            ret_value = NO_APIC;
+        }
+
+    return ret_value;
+}
+
diff --git a/i386/i386at/acpi_parse_apic.h b/i386/i386at/acpi_parse_apic.h
new file mode 100644
index 00000000..7652d30e
--- /dev/null
+++ b/i386/i386at/acpi_parse_apic.h
@@ -0,0 +1,142 @@
+/*Copyright 2018 Juan Bosco Garcia
+ *This file is part of Min_SMP.
+ *Min_SMP 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.
+ *Min_SMP 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 Min_SMP.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ACPI_H__
+#define __ACPI_H__
+
+#include <include/stdint.h> //uint16_t, uint32_t_t...
+
+
+#define ACPI_RSDP_ALIGN 16
+#define ACPI_RSDP_SIG "RSD PTR "
+
+
+struct acpi_rsdp
+{
+    uint8_t signature[8];
+    uint8_t checksum;
+    uint8_t oem_id[6];
+    uint8_t revision[1];
+    uint32_t rsdt_addr;
+} __attribute__((__packed__));
+
+
+/* RSDT Entry Header
+ *
+ * Header which stores the descriptors of tables pointed from RDSP's Entry Field
+ * Includes the signature of the table, to identify each table
+ *
+ * In MADT, the signature is 'APIC'
+ */
+struct acpi_dhdr
+{
+    uint8_t signature[4];
+    uint32_t length;
+    uint8_t revision;
+    uint8_t checksum;
+    uint8_t oem_id[6];
+    uint8_t oem_table_id[8];
+    uint32_t oem_revision;
+    uint8_t creator_id[4];
+    uint32_t creator_revision;
+} __attribute__((__packed__));
+
+
+#define ACPI_RSDT_SIG "RSDT"
+
+struct acpi_rsdt
+{
+    struct acpi_dhdr header;
+    uint32_t entry[0];
+} __attribute__((__packed__));
+
+/* APIC table signature */
+#define ACPI_APIC_SIG "APIC"
+
+/* Types value for MADT entries: Local APIC, IOAPIC and IRQ Override */
+#define ACPI_APIC_ENTRY_LAPIC  0
+#define ACPI_APIC_ENTRY_IOAPIC 1
+#define ACPI_APIC_IRQ_OVERRIDE 2
+#define ACPI_APIC_ENTRY_NONMASK_IRQ 4
+
+/* APIC descriptor header
+ * Define the type of the structure (Local APIC, I/O APIC or others)
+ * Type: Local APIC (0), I/O APIC (1)
+ */
+struct acpi_apic_dhdr
+{
+    uint8_t type;
+    uint8_t length;
+} __attribute__((__packed__));
+
+
+/* Multiple APIC Description Table (MADT)
+ *
+ * Describes the APIC structures which exist in the machine
+ * Includes the common address where Local APIC is mapped in main memory
+ *
+ * Entry field stores the descriptors of APIC structures
+ */
+struct acpi_apic
+{
+    struct acpi_dhdr header; //Header, which stores the descriptor for RDST's Entry field
+    uint32_t lapic_addr; //Local Interrupt Controller Address
+    uint32_t flags;
+    struct acpi_apic_dhdr entry[0]; //Interrupt Controller Structure
+} __attribute__((__packed__));
+
+/* Processor Local APIC Structure
+ *
+ * Stores information about APIC ID, flags and ACPI Processor UID
+ */
+
+struct acpi_apic_lapic
+{
+    struct acpi_apic_dhdr header;
+    uint8_t processor_id; //ACPI Processor UID
+    uint8_t apic_id;
+    uint32_t flags;
+} __attribute__((__packed__));
+
+
+/* I/O APIC Structure
+ *
+ * Stores information about APIC ID, and I/O APIC tables
+ */
+
+struct acpi_apic_ioapic
+{
+    struct acpi_apic_dhdr header;
+    uint8_t apic_id;
+    uint8_t reserved;
+    uint32_t addr;
+    uint32_t base;
+} __attribute__((__packed__));
+
+
+struct acpi_apic_irq_override
+{
+    struct acpi_apic_dhdr header;
+    uint8_t bus;
+    uint8_t irq;
+    uint32_t gsi;
+    uint16_t flags;
+} __attribute__((__packed__));
+
+int acpi_apic_init(void);
+void acpi_print_info(struct acpi_rsdp *rsdp, struct acpi_rsdt *rsdt, int acpi_rsdt_n);
+
+
+#endif /* __ACPI_H__ */
+
diff --git a/i386/i386at/model_dep.c b/i386/i386at/model_dep.c
index aaeed807..75a902d5 100644
--- a/i386/i386at/model_dep.c
+++ b/i386/i386at/model_dep.c
@@ -72,6 +72,8 @@
 #include <i386at/kd.h>
 #include <i386at/rtc.h>
 #include <i386at/model_dep.h>
+#include <kern/smp.h>
+
 #ifdef	MACH_XEN
 #include <xen/console.h>
 #include <xen/store.h>
@@ -170,6 +172,14 @@ void machine_init(void)
 	linux_init();
 #endif
 
+#if NCPUS > 1
+	int smp_success = smp_init();
+
+	if(smp_success != 0) {
+        printf("Error: no SMP found");
+    }
+#endif // NCPUS > 1
+
 	/*
 	 * Find the devices
 	 */
diff --git a/configfrag.ac b/configfrag.ac
index 91d737ef..454d9f23 100644
--- a/configfrag.ac
+++ b/configfrag.ac
@@ -46,9 +46,12 @@ AC_DEFINE([BOOTSTRAP_SYMBOLS], [0], [BOOTSTRAP_SYMBOLS])
 # Multiprocessor support is still broken.
 AH_TEMPLATE([MULTIPROCESSOR], [set things up for a uniprocessor])
 mach_ncpus=1
+AC_DEFINE_UNQUOTED([NCPUS], [$mach_ncpus], [number of CPUs])
+
 AC_DEFINE_UNQUOTED([NCPUS], [$mach_ncpus], [number of CPUs])
 [if [ $mach_ncpus -gt 1 ]; then]
   AC_DEFINE([MULTIPROCESSOR], [1], [set things up for a multiprocessor])
+  AC_DEFINE_UNQUOTED([NCPUS], [255], [number of CPUs])
 [fi]
 
 # Restartable Atomic Sequences to get a really fast test-n-set.  Can't be
diff --git a/vm/vm_kern.c b/vm/vm_kern.c
index 2e333ee1..ce97bad3 100644
--- a/vm/vm_kern.c
+++ b/vm/vm_kern.c
 
+/* kmem_map_aligned_table: map a table or structure in a virtual memory page
+ * Align the table initial address with the page initial address
+ *
+ * Parameters:
+ * phys_address: physical address, the start address of the table
+ * size: size of the table
+ * mode: access mode. VM_PROT_READ for read, VM_PROT_WRITE for write
+ *
+ * Returns a reference to the virtual address if success, NULL if failure
+ */
+
+void*
+kmem_map_aligned_table (unsigned long phys_address, unsigned long size, int mode)
+{
+    vm_offset_t virt_addr;
+    kern_return_t ret;
+    uintptr_t into_page = phys_address % PAGE_SIZE;
+    uintptr_t nearest_page = (uintptr_t)trunc_page(phys_address);
+
+    size += into_page;
+
+    ret = kmem_alloc_wired(kernel_map, &virt_addr, round_page (size));
+
+    if (ret != KERN_SUCCESS)
+        return NULL;
+
+    (void) pmap_map_bd (virt_addr, nearest_page, nearest_page + round_page (size), mode);
+
+    /* XXX remember mapping somewhere so we can free it? */
+
+    return (void *) (virt_addr + into_page);
+}
+

diff --git a/vm/vm_kern.h b/vm/vm_kern.h
index 0cdb19db..46a1a274 100644
--- a/vm/vm_kern.h
+++ b/vm/vm_kern.h
@@ -55,6 +55,8 @@ extern kern_return_t	kmem_alloc_pageable(vm_map_t, vm_offset_t *,
 extern kern_return_t	kmem_valloc(vm_map_t, vm_offset_t *, vm_size_t);
 extern kern_return_t	kmem_alloc_wired(vm_map_t, vm_offset_t *, vm_size_t);
 extern kern_return_t	kmem_alloc_aligned(vm_map_t, vm_offset_t *, vm_size_t);
+extern void*            kmem_map_aligned_table (unsigned long phys_address, unsigned long size, int mode);
+
 extern void		kmem_free(vm_map_t, vm_offset_t, vm_size_t);
 
 extern void		kmem_submap(vm_map_t, vm_map_t, vm_offset_t *,
diff --git a/kern/smp.c b/kern/smp.c
new file mode 100644
index 00000000..2f5f16ee
--- /dev/null
+++ b/kern/smp.c
@@ -0,0 +1,69 @@
+/* smp.c - Template for generic 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 <kern/smp.h>
+
+struct smp_data smp_info;
+
+#ifdef __i386__
+#include <i386/i386/apic.h>
+#include <i386/i386at/acpi_parse_apic.h>
+
+/* smp_data_init: initialize smp_data structure
+ * Must be called after smp_init(), once all APIC structures
+ * has been initialized
+ */
+
+void smp_data_init(void)
+{
+    smp_info.num_cpus = apic_get_numcpus();
+}
+
+/* smp_get_numcpus: returns the number of cpus existing in the machine */
+
+int smp_get_numcpus(void)
+{
+    return smp_info.num_cpus;
+}
+
+/* smp_get_current_cpu: return the hardware identifier (APIC ID in x86) of current CPU */
+
+int smp_get_current_cpu(void)
+{
+    return apic_get_current_cpu();
+}
+
+/* smp_init: initialize the SMP support, starting the cpus searching
+ *  and enumeration */
+
+int smp_init(void)
+{
+    int apic_success;
+
+    apic_success = acpi_apic_init();
+    if (apic_success)
+        {
+            smp_data_init();
+        }
+
+    return apic_success;
+}
+
+#endif
diff --git a/kern/smp.h b/kern/smp.h
new file mode 100644
index 00000000..f03dab8c
--- /dev/null
+++ b/kern/smp.h
@@ -0,0 +1,29 @@
+/* smp.h - Template for generic SMP controller for Mach. Header file
+   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. */
+
+struct smp_data
+{
+    int num_cpus;
+};
+
+void smp_data_init(void);
+int smp_get_numcpus(void);
+int smp_get_current_cpu(void);
+int smp_init(void);

diff --git a/Makefrag.am b/Makefrag.am
index ea612275..69ac31a1 100644
--- a/Makefrag.am
+++ b/Makefrag.am
@@ -122,6 +122,18 @@ EXTRA_DIST += \
 	ipc/notify.defs
 
 
+#
+# SMP implementation (APIC, ACPI, etc)
+#
+
+libkernel_a_SOURCES += \
+	i386/i386at/acpi_parse_apic.h \
+	i386/i386at/acpi_parse_apic.c \
+	i386/i386/apic.h \
+	i386/i386/apic.c \
+	kern/smp.h \
+	kern/smp.c
+
 #
 # `kernel' implementation (tasks, threads, trivia, etc.).
 #
diff --git a/i386/i386/apic.c b/i386/i386/apic.c
new file mode 100644
index 00000000..ddc51afb
--- /dev/null
+++ b/i386/i386/apic.c
@@ -0,0 +1,257 @@
+/* apic.c - APIC controller management 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 <i386/apic.h>
+#include <string.h>
+#include <vm/vm_kern.h>
+#include <kern/printf.h> //printf
+
+
+#define MAX_CPUS 256
+
+volatile ApicLocalUnit* lapic = NULL;
+
+struct apic_info apic_data;
+
+/* apic_data_init: initialize the apic_data structures to preliminary values
+ *  Reserve memory to the lapic list dynamic vector
+ *  Returns 0 if success, -1 if error
+ */
+
+int
+apic_data_init(void)
+{
+    int success = 0;
+
+    apic_data.cpu_lapic_list = NULL;
+    apic_data.ncpus = 0;
+    apic_data.nioapics = 0;
+    apic_data.cpu_lapic_list = (uint16_t*) kalloc(MAX_CPUS*sizeof(uint16_t));
+    apic_data.nirqoverride = 0;
+
+    if(apic_data.cpu_lapic_list == NULL)
+        {
+            success = -1;
+        }
+
+    return success;
+}
+
+/* apic_lapic_init: initialize lapic pointer to the memory common address
+ *    Receives as input a pointer to the virtual memory address, previously mapped in a page
+ */
+
+void
+apic_lapic_init(ApicLocalUnit* lapic_ptr)
+{
+    lapic = lapic_ptr;
+}
+
+/* apic_add_cpu: add a new lapic/cpu entry to the cpu_lapic list
+ *   Receives as input the lapic's APIC ID
+ */
+
+void
+apic_add_cpu(uint16_t apic_id)
+{
+    int numcpus = apic_data.ncpus;
+    apic_data.cpu_lapic_list[numcpus] = apic_id;
+    apic_data.ncpus++;
+}
+
+/* apic_add_ioapic: add a new ioapic entry to the ioapic list
+ *   Receives as input an ioapic_data structure, filled with the IOAPIC entry's data
+ */
+
+void
+apic_add_ioapic(struct ioapic_data ioapic)
+{
+    int nioapic = apic_data.nioapics;
+
+    apic_data.ioapic_list[nioapic] = ioapic;
+
+    apic_data.nioapics++;
+}
+
+/* apic_add_irq_override: add a new IRQ to the irq_override list
+ *   Receives as input an irq_override_data structure, filled with the IRQ entry's data
+ */
+
+void
+apic_add_irq_override(struct irq_override_data irq_over)
+{
+    int nirq = apic_data.nirqoverride;
+
+    apic_data.irq_override_list[nirq] = irq_over;
+    apic_data.nirqoverride++;
+}
+
+/* apic_get_cpu_apic_id: returns the apic_id of a cpu
+ *   Receives as input the kernel ID of a CPU
+ */
+
+uint16_t
+apic_get_cpu_apic_id(int kernel_id)
+{
+    uint16_t apic_id;
+
+    if(kernel_id < MAX_CPUS)
+        {
+            apic_id = apic_data.cpu_lapic_list[kernel_id];
+        }
+    else
+        {
+            apic_id = -1;
+        }
+
+    return apic_id;
+}
+
+/* apic_get_lapic: returns a reference to the common memory address for Local APIC */
+
+ApicLocalUnit*
+apic_get_lapic(void)
+{
+    return lapic;
+}
+
+/* apic_get_ioapic: returns the IOAPIC identified by its kernel ID
+ *   Receives as input the IOAPIC's Kernel ID
+ *   Returns a ioapic_data structure with the IOAPIC's data
+ */
+
+struct ioapic_data
+apic_get_ioapic(int kernel_id)
+{
+    struct ioapic_data io_apic;
+
+    if(kernel_id < 16)
+        {
+            io_apic = apic_data.ioapic_list[kernel_id];
+        }
+
+    return io_apic;
+}
+
+/* apic_get_numcpus: returns the current number of cpus */
+
+int
+apic_get_numcpus(void)
+{
+    return apic_data.ncpus;
+}
+
+/* apic_get_num_ioapics: returns the current number of ioapics */
+
+int
+apic_get_num_ioapics(void)
+{
+    return apic_data.nioapics;
+}
+
+/* apic_get_current_cpu: returns the apic_id of current cpu */
+
+uint16_t
+apic_get_current_cpu(void)
+{
+    uint16_t apic_id;
+
+    if(lapic == NULL)
+        {
+            apic_id = 0;
+        }
+    else
+        {
+            apic_id = lapic->apic_id.r;
+        }
+    return apic_id;
+}
+
+
+/* apic_refit_cpulist: adjust the size of cpu_lapic array to fit the real number of cpus
+ *   instead the maximum number
+ *
+ * Returns 0 if success, -1 if error
+ */
+
+int apic_refit_cpulist(void)
+{
+    uint16_t* old_list = apic_data.cpu_lapic_list;
+    uint16_t* new_list = (uint16_t*) kalloc(apic_data.ncpus*sizeof(uint16_t));
+    int i = 0;
+    int success = 0;
+
+
+    if(new_list != NULL && old_list != NULL)
+        {
+            for(i = 0; i < apic_data.ncpus; i++)
+                {
+                    new_list[i] = old_list[i];
+                }
+
+            apic_data.cpu_lapic_list = new_list;
+            kfree(old_list);
+        }
+    else
+        {
+            success = -1;
+        }
+
+    return success;
+}
+
+/* apic_print_info: shows the list of Local APIC and IOAPIC
+ *
+ * Shows each CPU and IOAPIC, with Its Kernel ID and APIC ID
+ */
+
+void apic_print_info(void)
+{
+    int i;
+    int ncpus, nioapics;
+
+    ncpus = apic_get_numcpus();
+    nioapics = apic_get_num_ioapics();
+
+    uint16_t lapic_id;
+    uint16_t ioapic_id;
+
+    struct ioapic_data ioapic;
+
+    printf("CPUS\n");
+    printf("-------------------------------------------------\n");
+    for(i = 0; i < ncpus; i++)
+        {
+            lapic_id = apic_get_cpu_apic_id(i);
+
+            printf("CPU %d - APIC ID %x\n", i, lapic_id);
+        }
+
+    printf("\nIOAPICS\n");
+    printf("-------------------------------------------------\n");
+
+    for(i = 0; i < nioapics; i++)
+        {
+            ioapic = apic_get_ioapic(i);
+            ioapic_id = ioapic.apic_id;
+            printf("IOAPIC %d - APIC ID %x\n", i, ioapic_id);
+        }
+}
diff --git a/i386/i386/apic.h b/i386/i386/apic.h
new file mode 100644
index 00000000..05b0181e
--- /dev/null
+++ b/i386/i386/apic.h
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 1994 The University of Utah and
+ * the Computer Systems Laboratory at the University of Utah (CSL).
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify and distribute this software is hereby
+ * granted provided that (1) source code retains these copyright, permission,
+ * and disclaimer notices, and (2) redistributions including binaries
+ * reproduce the notices in supporting documentation, and (3) all advertising
+ * materials mentioning features or use of this software display the following
+ * acknowledgement: ``This product includes software developed by the
+ * Computer Systems Laboratory at the University of Utah.''
+ *
+ * THE UNIVERSITY OF UTAH AND CSL ALLOW FREE USE OF THIS SOFTWARE IN ITS "AS
+ * IS" CONDITION.  THE UNIVERSITY OF UTAH AND CSL DISCLAIM ANY LIABILITY OF
+ * ANY KIND FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
+ *
+ * CSL requests users of this software to return to csl-d...@cs.utah.edu any
+ * improvements that they make and grant CSL redistribution rights.
+ *
+ *      Author: Bryan Ford, University of Utah CSL
+ */
+#ifndef _IMPS_APIC_
+#define _IMPS_APIC_
+
+#ifndef __ASSEMBLER__
+
+#include <stdint.h>
+
+typedef struct ApicReg
+{
+    unsigned r;	/* the actual register */
+    unsigned p[3];	/* pad to the next 128-bit boundary */
+} ApicReg;
+
+typedef struct ApicIoUnit
+{
+    ApicReg select;
+    ApicReg window;
+} ApicIoUnit;
+
+
+typedef struct ApicLocalUnit
+{
+    /* 0x000 */
+    ApicReg reserved0;
+    /* 0x010 */
+    ApicReg reserved1;
+    /* 0x020 */
+    ApicReg apic_id;
+    /* 0x030 */
+    ApicReg version;
+    /* 0x040 */
+    ApicReg reserved4;
+    /* 0x050 */
+    ApicReg reserved5;
+    /* 0x060 */
+    ApicReg reserved6;
+    /* 0x070 */
+    ApicReg reserved7;
+    /* 0x080 */
+    ApicReg task_pri;
+    /* 0x090 */
+    ApicReg arbitration_pri;
+    /* 0x0a0 */
+    ApicReg processor_pri;
+    /* 0x0b0 */
+    ApicReg eoi;
+    /* 0x0c0 */
+    ApicReg remote;
+    /* 0x0d0 */
+    ApicReg logical_dest;
+    /* 0x0e0 */
+    ApicReg dest_format;
+    /* 0x0f0 */
+    ApicReg spurious_vector;
+    /* 0x100 */
+    ApicReg isr[8];
+    /* 0x180 */
+    ApicReg tmr[8];
+    /* 0x200 */
+    ApicReg irr[8];
+    /* 0x280 */
+    ApicReg error_status;
+    /* 0x290 */
+    ApicReg reserved28[6];
+    /* 0x2f0 */
+    ApicReg lvt_cmci;
+    /* 0x300 */
+    ApicReg icr_low;
+    /* 0x310 */
+    ApicReg icr_high;
+    /* 0x320 */
+    ApicReg lvt_timer;
+    /* 0x330 */
+    ApicReg lvt_thermal;
+    /* 0x340 */
+    ApicReg lvt_performance_monitor;
+    /* 0x350 */
+    ApicReg lvt_lint0;
+    /* 0x360 */
+    ApicReg lvt_lint1;
+    /* 0x370 */
+    ApicReg lvt_error;
+    /* 0x380 */
+    ApicReg init_count;
+    /* 0x390 */
+    ApicReg cur_count;
+    /* 0x3a0 */
+    ApicReg reserved3a;
+    /* 0x3b0 */
+    ApicReg reserved3b;
+    /* 0x3c0 */
+    ApicReg reserved3c;
+    /* 0x3d0 */
+    ApicReg reserved3d;
+    /* 0x3e0 */
+    ApicReg divider_config;
+    /* 0x3f0 */
+    ApicReg reserved3f;
+} ApicLocalUnit;
+
+struct ioapic_data
+{
+    uint8_t apic_id;
+    uint32_t addr;
+    uint32_t base;
+};
+
+#define APIC_IRQ_OVERRIDE_ACTIVE_LOW 2
+#define APIC_IRQ_OVERRIDE_LEVEL_TRIGGERED 8
+
+struct irq_override_data
+{
+    uint8_t bus;
+    uint8_t irq;
+    uint32_t gsi;
+    uint16_t flags;
+};
+
+struct apic_info
+{
+    uint16_t ncpus;
+    uint16_t nioapics;
+    int nirqoverride;
+
+    uint16_t* cpu_lapic_list;
+    struct ioapic_data ioapic_list[16];
+    struct irq_override_data irq_override_list[24];
+};
+
+int apic_data_init(void);
+void apic_add_cpu(uint16_t apic_id);
+void apic_lapic_init(ApicLocalUnit* lapic_ptr);
+void apic_add_ioapic(struct ioapic_data);
+void apic_add_irq_override(struct irq_override_data irq_over);
+uint16_t apic_get_cpu_apic_id(int kernel_id);
+ApicLocalUnit* apic_get_lapic(void);
+struct ioapic_data apic_get_ioapic(int kernel_id);
+int apic_get_numcpus(void);
+int apic_get_num_ioapics(void);
+uint16_t apic_get_current_cpu(void);
+void apic_print_info(void);
+int apic_refit_cpulist(void);
+
+#endif
+
+#define APIC_IO_UNIT_ID			0x00
+#define APIC_IO_VERSION			0x01
+#define APIC_IO_REDIR_LOW(int_pin)	(0x10+(int_pin)*2)
+#define APIC_IO_REDIR_HIGH(int_pin)	(0x11+(int_pin)*2)
+
+/* Address at which the local unit is mapped in kernel virtual memory.
+ *   Must be constant.
+ */
+
+#define APIC_LOCAL_VA	lapic
+
+#define apic_local_unit (*((volatile ApicLocalUnit*)APIC_LOCAL_VA))
+
+
+/* Set or clear a bit in a 255-bit APIC mask register.
+   These registers are spread through eight 32-bit registers.  */
+#define APIC_SET_MASK_BIT(reg, bit) \
+	((reg)[(bit) >> 5].r |= 1 << ((bit) & 0x1f))
+#define APIC_CLEAR_MASK_BIT(reg, bit) \
+	((reg)[(bit) >> 5].r &= ~(1 << ((bit) & 0x1f)))
+
+#endif /*_IMPS_APIC_*/
+

Reply via email to