Правильная реализация SMP на платформе Linux/MIPS

Я пытался заставить поддержку SMP снова работать на порте ядра Linux/MIPS на SGI Octane (IP30) в течение последних нескольких недель. Поддержка Uniprocessor работает нормально, но у меня много проблем при работе со вторым процессором. Я могу загрузить машину на init процесс, но это умирает либо с SIGSEGV или SIGBUS большую часть времени. У меня есть большая часть кода поддержки из исправлений, написанных 5+ лет назад, но я подозреваю, что я либо неправильно блокирую, либо неожиданно включаю IRQ.


Некоторый фон оборудования:

Процессор MIPS серии R10000 реализует 8 прерываний, IP0 в IP7:

  • IP0 а также IP1: Только программное обеспечение прерывается и в настоящее время не используется много.
  • IP2 в IP6: Обычно направляется на другую аппаратную функцию для обработки
  • IP7: Прерывание таймера / счетчика / сравнения R10K.

  • R10K поддерживает ISA MIPS-IV и имеет I-кеш и D-кеш.

    • I-кэш имеет размер строки 32 КБ, VIPT, 2-полосный и 64-байтовый.
    • D-кэш 32 КБ, VIPT, двухсторонний, без псевдонимов и размер строки 32 байта.
  • Кэш-память второго уровня R10K имеет размер строки 2 МБ, 2-полосный и 128-байтовый.
  • R10K является суперскалярным, использует спекулятивное исполнение и может выполняться не по порядку.
  • Октан когерентен, поэтому не страдает от последствий спекулятивного выполнения.
  • В частности, у меня есть двойной модуль R14000 в этой системе. Об этом мало что известно, кроме того, что это в основном R10K с усадкой и более быстрой скоростью. SGI никогда не выпускала технические данные об этом процессоре и информацию об ошибках.


Октан имеет ASIC под названием HEART как его контроллер памяти и контроллер прерываний. HEART был разработан для поддержки до 4 процессоров и имеет 64 прерывания (IRQ). Эти 64 IRQ разделены на несколько уровней приоритета и сопоставлены с IRQ Rx IP CPU выше:

  • Уровень 0, IRQ 0 в 15 -> CPU IP2
  • Уровень 1, IRQ 16 в 31 -> CPU IP3
  • Уровень 2, IRQ 31 в 49 -> CPU IP4
  • Уровень 3, IRQ 50 -> CPU IP5
  • Уровень 4, IRQ 51 в 63 -> CPU IP6


Есть несколько примечаний об этих уровнях приоритета:

  • IRQ уровня 0 и уровня 1 в основном назначаются устройствам в системе (SCSI, Ethernet и т. Д.).

  • Уровень 2 имеет несколько применений:

    • IRQs 32 в 40 также доступны для использования устройствами в системе (особенно те, которые требуют более высокого приоритета).
    • IRQ 41 подключен для нажатия кнопки питания.
    • IRQs 42 в 45 предназначены для сигналов отладчика для 4 возможных процессоров.
    • IRQs 46 в 49 являются межпроцессорными прерываниями SMP (IPI) для 4 возможных процессоров.

  • Уровень 3, IRQ 50, специально для таймера счетчика / сравнения на HEART сам. Он работает на частоте 12,5 МГц (думаю, 80 нс). Он имеет регистр единого счета и регистр сравнения. Из линукса clockevent С точки зрения, я думаю, что это лучший таймер разрешения для использования в качестве системного таймера (52-битный счетчик, 24-битное сравнение).

  • Уровень 4 предназначен для ошибок IRQ:

    • IRQs 51 в 58 IRQ ошибок для каждого из 8 доступных Xtalk виджеты на шине XIO (высокоскоростная шина, расположенная в топологии "звезда", обслуживаемая XBOW ASIC).
    • IRQs 59 в 62 IRQ ошибок шины для 4 возможных процессоров.
    • IRQ 63 ошибка исключения IRQ для HEART сам.

HEART представлены несколько регистров для работы с прерываниями. Каждый регистр имеет ширину 64 бита, один бит на прерывание:

  • HEART_ISR - Только для чтения зарегистрироваться, чтобы получить список ожидающих прерываний.
  • HEART_SET_ISR - Регистр только для записи, чтобы установить определенный бит прерывания.
  • HEART_CLR_ISR - Регистр только для записи, чтобы очистить определенный бит прерывания
  • HEAR_IMR(x) - Чтение / запись регистра для установки или сброса маски прерывания для определенного прерывания на конкретном ЦП, представленного x,


Я использую следующий код для базовых операций подтверждения / маскирования / снятия маски с IRQ

u64 *imr;                       /* Address of the mask register to work on */
static int heart_irq_owner[64]; /* Which CPU owns which IRQ? (global) */

Ack:    writeq((1UL << irq), HEART_CLR_ISR);

Mask:   imr = HEART_IMR(heart_irq_owner[irq]);
        writeq(readq(imr) & (~(1UL << irq)), imr);

Unmask: imr = HEART_IMR(heart_irq_owner[irq]);
        writeq(readq(imr) | (1UL << irq), imr);


Эти основные операции реализуются с использованием struct irq_chip средства доступа в ядре Linux серии 3.1x, и я защищаю доступ к HEART регистры с использованием spin_lock_irqsave а также spin_unlock_irqrestore, Я не уверен на 100%, стоит ли мне использовать эти функции блокировки в этих средствах доступа.



Для обработки всех прерываний стандартная функция диспетчеризации платформы Linux/MIPS выполняет следующие действия:

  • IP7 -> Звонки do_IRQ() обрабатывать таймер процессора IRQ.
  • IP6 -> Звонки ip30_do_error_irq() сообщить о любом HEART ошибки в системном журнале.
  • IP5 -> Звонки do_IRQ() обрабатывать IRQ ClockEvent, назначенный для HEART таймер.
  • IP4, IP3, а также IP2 -> Звонки ip30_do_heart_irq() справиться со всеми HEART IRQ от 0 до 49.


Этот код в настоящее время используется для ip30_do_heart_irq():

static noinline void ip30_do_heart_irq(void)
{
    int irqnum = 49;
    int cpu = smp_processor_id();
    u64 heart_isr = readq(HEART_ISR);
    u64 heart_imr = readq(HEART_IMR(cpu));
    u64 irqs = (heart_isr & 0x0003ffffffffffffULL &
                heart_imr);

    /* Poll all IRQs in decreasing priority order */
    do {
        if (irqs & (1UL << irqnum))
            do_IRQ(irqnum);
        irqnum--;
    } while (likely(irqnum >= 0));
}


Когда дело доходит до поддержки SMP, в отличие от других платформ Linux/MIPS, у меня нет чего-то похожего на реестр почтовых ящиков в аппаратном обеспечении для хранения того, какие действия IPI следует предпринять. Оригинальный код использует глобальный массив int (ip30_ipi_mailbox), проиндексированный CPUID, для указания, какое действие IPI передать другому процессору.

Кроме того, даже если HEART был спроектирован для поддержки до 4 процессоров, SGI выпускала только двухпроцессорные модули. Поэтому IRQ 44-45, 48-49, а также 61-62 на самом деле никогда не используются ни для чего.

Учитывая эти глобальные переменные:

#define IPI_CPU(x) (46 + (x))
static DEFINE_SPINLOCK(ip30_ipi_lock);
static u32 ip30_ipi_mailbox[4];


Этот код в настоящее время используется для отправки IPI другим процессорам:

static void ip30_send_ipi_single(int cpu, u32 action)
{
    unsigned long flags;

    spin_lock_irqsave(&ip30_ipi_lock, flags);
    ip30_ipi_mailbox[cpu] |= action;
    spin_unlock_irqrestore(&ip30_ipi_lock, flags);
    writeq(1UL << IPI_CPU(cpu)), HEART_SET_ISR);
}


Чтобы ответить на IPI, каждый процессор вызывает request_irq в своем коде инициализации и регистрирует обработчик прерываний. Этот код в настоящее время используется в обработчике для обслуживания прерывания IPI:

static irqreturn_t ip30_ipi_irq(int irq, void *dev_id)
{
    u32 action;
    int cpu = smp_processor_id();
    unsigned long flags;

    spin_lock_irqsave(&ip30_ipi_lock, flags);
    action = ip30_ipi_mailbox[cpu];
    ip30_ipi_mailbox[cpu] = 0;
    spin_unlock_irqrestore(&ip30_ipi_lock, flags);

    if (action & SMP_RESCHEDULE_YOURSELF)
        scheduler_ipi();

    if (action & SMP_CALL_FUNCTION)
        smp_call_function_interrupt();

    return IRQ_HANDLED;
}



И это справочная информация.

Моя текущая конфигурация ядра содержит все, кроме кадрового буфера и видеодрайвера Impact. Нет PCI, нет блочного уровня, нет сети, нет последовательного интерфейса, нет клавиатуры / мыши. У меня есть ~7-летние initramfs, которые я загружаю, и, если все работает, следует перейти к приглашению bash. Однако, поскольку он загружается в ОЗУ, он способен довольно быстро выявлять повреждения памяти, и в результате я получаю либо вышеупомянутые ошибки SIGSEGV или SIGBUS.

Использование удаленной GDB или встроенной KGDB в настоящее время недоступно из-за устройства PCI IOC3. IOC3 - это многофункциональное PCI-устройство, которое претендует на то, чтобы быть однофункциональным устройством, и за ним лежат аппаратные биты для клавиатуры / мыши, последовательных портов, часов реального времени и Ethernet. Код еще не существует, чтобы обойти IOC3 и получить доступ к последовательным портам напрямую для удаленной GDB, и KGDB также не знает, как разговаривать со стандартным контроллером клавиатуры i8042 на IOC3.

У меня добавлена ​​стандартная последовательная плата PCI (на базе Moschip), но этот драйвер, очевидно, небезопасен, так что поиск последовательных портов вызывает панику в ядре.


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

  • Правильно ли я использую спинлок?
  • Я использую правильные варианты спин-блокировки?
  • Нужно ли добавлять синхронизирующие вызовы в любом месте (т.е. smp_rmb(), smp_wmb(), так далее)?
  • Может ли моя проблема лежать вне кода поддержки этой базовой платформы (например, в драйвере видео)?
  • Могу ли я в случайном порядке искать неизвестную аппаратную ошибку, повреждающую память?
  • Может ли какой-либо из приведенных выше кодов быть реализован лучше? (большая часть кода - это код от исходного порта Linux 2.6.17 до Octane, только что обновленный, чтобы больше соответствовать тому, как работают другие компоненты ядра)

Любая информация, которая может поставить меня на правильный путь для выяснения этого, будет принята с благодарностью. Я надеюсь, что SMP перейдет в работоспособное состояние (эффективность не имеет значения, мне просто нужно, чтобы он работал), поэтому я могу начать работу по разбивке вещей на патчи и посмотреть, как в какой-то момент включить их в основное ядро. Если я не могу заставить работать SMP, я просто откажусь от его поддержки и сконцентрируюсь на том, чтобы вместо этого отправлять однопроцессорный код.

2 ответа

Решение

В конечном итоге ошибка была исправлена, так как номера IRQ не были назначены их правильному обработчику. Я изначально назначал ВСЕ 64 IRQ для использования handle_level_irq, что неверно для межпроцессорных прерываний SMP (IPI). Исправление оказалось назначить 8 специфичных для CPU прерываний, 42-45 и 46-49, для handle_percpu_irq вместо.

Этот случай очень интересен: особенно если есть способ его воспроизвести. К сожалению у меня его нет:) . Но есть много вещей, которые я хотел бы проверить, если мне нужно исправить проблему: 1- отключить все уровни кэширования (на процессор), чтобы отбросить неисправный механизм отслеживания и синхронизации кэша. 2- Свяжите все прерывания на одном процессоре и загрузите плату. Если проблема возникает, то я бы сказал, что код, добавленный вами для обработки прерываний в SMP, не является виновным. Пожалуйста, установите привязку IRQ к одному процессору (скажем, cpu0). Для этого теста оставьте свой код таким, какой он есть... Просто убедитесь, что изменили привязку прерываний к одному ЦП.

Не стесняйтесь поделиться результатами. Надеюсь, это поможет.

Aymen.

Другие вопросы по тегам