Правильная реализация 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
-> CPUIP2
- Уровень 1, IRQ
16
в31
-> CPUIP3
- Уровень 2, IRQ
31
в49
-> CPUIP4
- Уровень 3, IRQ
50
-> CPUIP5
- Уровень 4, IRQ
51
в63
-> CPUIP6
Есть несколько примечаний об этих уровнях приоритета:
IRQ уровня 0 и уровня 1 в основном назначаются устройствам в системе (SCSI, Ethernet и т. Д.).
Уровень 2 имеет несколько применений:
- IRQs
32
в40
также доступны для использования устройствами в системе (особенно те, которые требуют более высокого приоритета). - IRQ
41
подключен для нажатия кнопки питания. - IRQs
42
в45
предназначены для сигналов отладчика для 4 возможных процессоров. - IRQs
46
в49
являются межпроцессорными прерываниями SMP (IPI) для 4 возможных процессоров.
- IRQs
Уровень 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
сам.
- IRQs
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.