Подтвердить что ты не робот

ARM: запуск/пробуждение/вывод других ядер процессора/точек доступа и запуск начального адреса запуска?

Я бил головой об этом в течение последних 3-4 дней, и я не могу найти объяснительную документацию DECENT (от ARM или неофициальную), чтобы помочь мне. У меня есть плата ODROID-XU (большая. LITTLE 2 x Cortex-A15 + 2 x Cortex-A7), и я пытаюсь понять немного больше об архитектуре ARM. В моем "экспериментирующем" коде теперь я пришел на сцену, где я хочу ОБРАТИТЬ ДРУГИЕ КОРЫ ИЗ ИХ WIF (ожидания для прерывания).

Отсутствующая информация, которую я все еще пытаюсь найти, это:

1. При получении базового адреса GIC-карты GIC я понимаю, что мне нужно прочитать CBAR; Но никакая часть документации не объясняет, как биты в CBAR (2 значения PERIPHBASE) должны быть организованы, чтобы добраться до конечного базового адреса GIC

2. При отправке SGI через регистр GICD_SGIR, какой идентификатор прерывания между 0 и 15 должен выбрать? Это имеет значение?

3. При отправке SGI через регистр GICD_SGIR, как я могу рассказать о других ядрах ГДЕ НАЧАТЬ ИСПОЛНЕНИЕ ОТ?

4. Как влияет на этот контекст тот факт, что мой код загружен загрузчиком U-BOOT?

Руководство по программированию Cortex-A v3.0 (найдено здесь: ссылка) гласит следующее в разделе 22.5. 2 (загрузка SMP в Linux, страница 271):

В то время как основное ядро ​​загружается, вторичные ядра будут находиться в режиме ожидания, используя Инструкция WFI. Он (первичное ядро) предоставит начальный адрес вторичным ядрам и разбудит их, используя Inter-Processor Interrupt (IPI), что означает SGI, сигнализированный через GIC

Как Linux это делает? Документация S не содержит никаких других подробностей относительно ". Она предоставит адрес запуска для вторичных ядер".

Мое разочарование растет, и я буду очень благодарен за ответы. Большое вам спасибо заранее!

ДОПОЛНИТЕЛЬНЫЕ ДАННЫЕ

Документация, которую я использую:

  • Справочное руководство по архитектуре ARMv7-A & R
  • Cortex-A15 TRM (техническое справочное руководство)
  • Cortex-A15 MPCore TRM
  • Руководство для программистов серии Cortex-A v3.0
  • Спецификация архитектуры GICv2

Что я сделал сейчас:

  • UBOOT загружает меня на 0x40008000; Я установил таблицы перевода (TTB), написал соответственно TTBR0 и TTBCR и отобразил 0x40008000 на 0x8000_0000 (2 ГБ), поэтому я также включил MMU
  • Настройка обработчиков исключений моего собственного
  • У меня есть функция Printf над последовательностью (UART2 на ODROID-XU)

Все вышеизложенное работает нормально.

То, что я пытаюсь сделать сейчас:

  • Получить базовый адрес GIC = > в тот момент, когда я читаю CBAR, и я просто AND (&) его значение с 0xFFFF8000 и использовать это как базовый адрес GIC, хотя я почти уверен, что это не вправо
  • Включить дистрибьютор GIC (со смещением 0x1000 от базового адреса GIC?), нажимая GICD_CTLR со значением 0x1
  • Построить SGI со следующими параметрами: Group = 0, ID = 0, TargetListFilter = "Все CPU за исключением меня" и отправить его (записать) через регистр GICD_SGIR GIC
  • Поскольку я не передал начальный адрес запуска для других ядер, ничего не происходит после всего этого

.... UPDATE....

Я начал смотреть на ядро ​​Linux и исходные коды QEMU в поисках ответа. Вот что я узнал (пожалуйста, поправьте меня, если я ошибаюсь):

  • При включении платы ВСЕ КОРЫ начинают выполнение из reset vector
  • Компонент программного обеспечения (прошивки) выполняет WFI на вторичных ядрах и некоторый другой код, который будет действовать как протокол между этими вторичными ядрами и основным ядром, когда последний хочет снова разбудить их
  • Например, протокол, используемый на плате EnergyCore ECX-1000 (Highbank), выглядит следующим образом:

**(1)** the secondary cores enter WFI and when

**(2)** the primary core sends an SGI to wake them up

**(3)** they check if the value at address (0x40 + 0x10 * coreid) is non-null;

**(4)** if it is non-null, they use it as an address to jump to (execute a BX)

**(5)** otherwise, they re-enter standby state, by re-executing WFI

**(6)** So, if I had an EnergyCore ECX-1000 board, I should write (0x40 + 0x10 * coreid) with the address I want each of the cores to jump to and send an SGI

Вопросы:

  • 1. Что такое программный компонент, который делает это? Является ли это бинарным BL1, который я написал на SD-карте, или это U-BOOT?
  • 2. Насколько я понимаю, этот программный протокол отличается от платы. Это так или зависит только от основного процессора?
  • 3. Где я могу найти информацию об этом протоколе для одной платы ARM? - могу ли я найти его на официальном веб-сайте ARM или на веб-странице борта?
4b9b3361

Ответ 1

Хорошо, я вернулся. Вот выводы:

  • Программный компонент, который заставляет процессоры спать, - это загрузчик (в моем случае U-Boot)
  • Linux каким-то образом знает, как это делает загрузчик (жестко закодированный в ядре Linux для каждой платы) и знает, как разбудить их снова

Для моей платы ODROID-XU источники, описывающие этот процесс, UBOOT ODROID-v2012.07 и ядро ​​linux найдено здесь: LINUX ODROIDXU-3.4.y (было бы лучше, если бы я посмотрел версию ядра из ветки odroid-3.12.y, поскольку первая не запустите все 8 процессоров, всего 4 из них, но последний делает).

В любом случае, здесь исходный код, который я придумал, я разместим соответствующие исходные файлы из приведенных выше деревьев исходного кода, которые помогли мне написать этот код впоследствии:

typedef unsigned int DWORD;
typedef unsigned char BOOLEAN;
#define FAILURE (0)
#define SUCCESS (1)
#define NR_EXTRA_CPUS (3) // actually 7, but this kernel version can't wake them up all -> check kernel version 3.12 if you need this

// Hardcoded in the kernel and in U-Boot; here I've put the physical addresses for ease
// In my code (and in the linux kernel) these addresses are actually virtual
// (thus the 'VA' part in S5P_VA_...); note: mapped with memory type DEVICE
#define S5P_VA_CHIPID (0x10000000)
#define S5P_VA_SYSRAM_NS (0x02073000)
#define S5P_VA_PMU (0x10040000)
#define EXYNOS_SWRESET ((DWORD) S5P_VA_PMU + 0x0400)
// Other hardcoded values
#define EXYNOS5410_REV_1_0 (0x10)
#define EXYNOS_CORE_LOCAL_PWR_EN (0x3)

BOOLEAN BootAllSecondaryCPUs(void* CPUExecutionAddress){

// 1. Get bootBase (the address where we need to write the address where the woken CPUs will jump to)
//    and powerBase (we also need to power up the cpus before waking them up (?))
DWORD bootBase, powerBase, powerOffset, clusterID;

asm volatile ("mrc p15, 0, %0, c0, c0, 5" : "=r" (clusterID));
clusterID = (clusterID >> 8);
powerOffset = 0;
if( (*(DWORD*)S5P_VA_CHIPID & 0xFF) < EXYNOS5410_REV_1_0 )
{
    if( (clusterID & 0x1) == 0 ) powerOffset = 4;
}
else if( (clusterID & 0x1) != 0 ) powerOffset = 4;

bootBase = S5P_VA_SYSRAM_NS + 0x1C;
powerBase = (S5P_VA_PMU + 0x2000) + (powerOffset * 0x80);

// 2. Power up each CPU, write bootBase and send a SEV (they are in WFE [wait-for-event] standby state)
for (i = 1; i <= NR_EXTRA_CPUS; i++)
{
    // 2.1 Power up this CPU
    powerBase += 0x80;
    DWORD powerStatus = *(DWORD*)( (DWORD) powerBase + 0x4);

    if ((powerStatus & EXYNOS_CORE_LOCAL_PWR_EN) == 0)
    {
        *(DWORD*) powerBase = EXYNOS_CORE_LOCAL_PWR_EN;
        for (i = 0; i < 10; i++) // 10 millis timeout
        {
            powerStatus = *(DWORD*)((DWORD) powerBase + 0x4);
            if ((powerStatus & EXYNOS_CORE_LOCAL_PWR_EN) == EXYNOS_CORE_LOCAL_PWR_EN)
                break;
            DelayMilliseconds(1); // not implemented here, if you need this, post a comment request 
        }
        if ((powerStatus & EXYNOS_CORE_LOCAL_PWR_EN) != EXYNOS_CORE_LOCAL_PWR_EN)
            return FAILURE;
    }
    if ( (clusterID & 0x0F) != 0 )
    {
        if ( *(DWORD*)(S5P_VA_PMU + 0x0908) == 0 )
        do { DelayMicroseconds(10); } // not implemented here, if you need this, post a comment request
        while (*(DWORD*)(S5P_VA_PMU + 0x0908) == 0);
        *(DWORD*) EXYNOS_SWRESET = (DWORD)(((1 << 20) | (1 << 8)) << i);
    }

    // 2.2 Write bootBase and execute a SEV to finally wake up the CPUs
    asm volatile ("dmb" : : : "memory");
    *(DWORD*) bootBase = (DWORD) CPUExecutionAddress;
    asm volatile ("isb");
    asm volatile ("\n   dsb\n   sev\n   nop\n");
}
return SUCCESS;
}

Это успешно пробуждает 3 из 7 вторичных процессоров.

И теперь для этого короткого списка соответствующих исходных файлов в u-boot и ядре linux:

  • UBOOT: lowlevel_init.S - обратите внимание на строки 363-369, как вторичные процессоры подождите в WFE, чтобы значение _hotplug_addr не было обнулено и чтобы перейти к нему; _hotplug_addr - фактически bootBase в приведенном выше коде; также строки 282-285 сообщают нам, что _hotplug_addr должен быть перемещен в CONFIG_PHY_IRAM_NS_BASE + _hotplug_addr - nscode_base ( _hotplug_addr - nscode_base 0x1C и CONFIG_PHY_IRAM_NS_BASE - 0x02073000, поэтому указанные выше жесткие коды в ядре Linux)

  • LINUX KERNEL: generic - smp.c (посмотрите на функцию __ cpu_up), < сильная > специфичная для платформы (odroid-xu): platsmp.c (функция boot_secondary, вызывается общим __cpu_up; platform_smp_prepare_cpus [внизу] = > , что функция, которая фактически устанавливает базовые значения загрузки и мощности)

Ответ 2

Для ясности и будущей ссылки здесь отсутствует небольшая часть информации из-за отсутствия надлежащей документации по протоколу загрузки Exynos (nb этот вопрос действительно должен быть помечен как "Exynos 5", а не "Cortex-A15" - it специфичная для SoC вещь и то, что говорит ARM, является лишь общей рекомендацией). При холодном загрузке вторичные ядра не находятся в WFI, они все еще отключены.

Более простое минимальное решение (на основе того, что делает Linux hotplug), которое я разработал в процессе написания загрузочной прокладки для получения гипервизора, работающего на XU, выполняет два шага:

  • Сначала напишите адрес точки входа в холдинг Exynos pen (0x02073000 + 0x1c)
  • Затем вытащите контроллер питания для включения соответствующего ядра (я). Таким образом, они выходят из безопасного пути загрузки в удерживающее перо, чтобы найти точку входа, ожидающую их, пропуская цикл WFI и устраняя необходимость даже коснуться GIC.

Если вы не планируете реализацию полнофункционального CPU hotplug, вы можете пропустить проверку идентификатора кластера - если мы загружаемся, мы находимся в кластере 0 и нигде больше (проверка на наличие готовых чипов с обратными кластерными регистрами также должно быть ненужным для Оддерода - конечно, для меня).

Из моего исследования, стрельба по A7s немного больше задействована. Судя по драйверу Exynos big.LITTLE, кажется, вам нужно выставить отдельный набор регистров контроллера мощности, чтобы сначала включить кластер 1 (и вы может понадобиться также объединиться с CCI, особенно для того, чтобы иметь MMU и кеши) - я не стал дальше, поскольку к этому моменту это было более "весело", чем "делать настоящую работу"...

В стороне, основной патч Samsung для CPU hotplug на 5410 делает основной элемент управления питанием более ясным, чем беспорядок в их нижнем коде, ИМО.

Ответ 3

Перейдите в www.arm.com и загрузите там оценочную копию пакета разработки DS-5. После установки под примерами будет startup_Cortex-A15MPCore directory. Посмотрите startup.s.

Ответ 4

QEMU использует PSCI

Интерфейс взаимодействия с политикой питания ARM (PSCI) документируется по адресу: https://developer.arm.com/docs/den0022/latest/arm-power-state-coordination-interface-platform-design-document и контролирует такие функции, как питание включения и выключения ядер.

TL; DR - это фрагмент aarch64 для запуска CPU 1 на QEMU v3.0.0 ARMv8 aarch64:

/* PSCI function identifier: CPU_ON. */
ldr w0, =0xc4000003
/* Argument 1: target_cpu */
mov x1, 1
/* Argument 2: entry_point_address */
ldr x2, =cpu1_entry_address
/* Argument 3: context_id */
mov x3, 0
/* Unused hvc args: the Linux kernel zeroes them,
 * but I don't think it is required.
 */
hvc 0

и для ARMv7:

ldr r0, =0x84000003
mov r1, #1
ldr r2, =cpu1_entry_address
mov r3, #0
hvc 0

В разделе ARM этого ответа доступен полный runnable пример с spinlock. Как выглядит язык многоуровневой сборки?

Затем инструкция hvc обрабатывается обработчиком EL2, см. Также раздел ARM: Что такое Ring 0 и Ring 3 в контексте операционных систем?

Ядро Linux

В Linux v4.19 этот адрес сообщается ядру Linux через дерево устройств, QEMU, например, автоматически генерирует запись формы:

    psci {
            method = "hvc";
            compatible = "arm,psci-0.2", "arm,psci";
            cpu_on = <0xc4000003>;
            migrate = <0xc4000005>;
            cpu_suspend = <0xc4000001>;
            cpu_off = <0x84000002>;
    };

Команда hvc вызывается из: https://github.com/torvalds/linux/blob/v4.19/drivers/firmware/psci.c#L178

static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point)

который заканчивается: https://github.com/torvalds/linux/blob/v4.19/arch/arm64/kernel/smccc-call.S#L51