Message Signaled Interrupts

The MSI Driver Guide HOWTO
Tom L Nguyen tom.l.nguyen@intel.com
10/03/2003
Revised Feb 12, 2004 by Martine Silbermann
email: Martine.Silbermann@hp.com
Revised Jun 25, 2004 by Tom L Nguyen
Revised Jul 9, 2008 by Matthew Wilcox
Copyright 2003, 2008 Intel Corporation

1. Об этом руководстве

В этом руководстве описаны основы Message Signaled Interrupts (MSIs), преимущества использования MSI, по сравнению с традиционным механизмом прерываний, как изменить Ваш драйвер, что бы он использовал MSI или MSI-X и некоторые методы диагностики, для определения причины, почему устройство не поддерживает MSI.

2. Что такое MSI ?

Message Signaled Interrupt - это запись из устройства в специальный адрес памяти, которая приводит к тому, что ЦП получает сигнал прерывания.

Возможности MSI впервые были определены в стандарте PCI 2.2 и, позднее, были доработаны в PCI 3.0 для того, что бы можно было индивидуально маскировать каждое MSI прерывание. Возможности MSI-X так же были добавлены в версии 3.0. Каждое устройство теперь может иметь большее число прерываний по сравнению с PCI и позволяет независимо настраивать каждое прерывание.

Устройство может поддерживать как MSI, так и MSI-X, но в любой момент времени может быть доступен только один из этих режимов.

3. Почему используют MSI ?

Использование MSI может дать три преимущества, по сравнению с использованием традиционной технологии линий прерывания.

Линии прерывания зачастую разделяются между несколькими устройствами. Для того, что бы обеспечить обработку разделяемых прерываний, ядро вынуждено вызывать поочерёдно все обработчики прерываний, связанный с этим прерыванием, что приводит к снижению производительности системы в целом. MSI никогда не разделяются, так что эта проблема не возникает.

Когда устройство завершает запись данных в память, возникшее прерывание может достигнуть процессора раньше, чем все данные реально достигнут памяти (если устройство расположено за PCI мостом, то вероятность этого возрастает). Для того, что бы удостоверится. что все данные реально записаны в память, обработчик прерываний должен прочитать любой регистр на устройстве, которое послало прерывание. Правила выполнения транзакций на PCI требуют, что бы все данные достигли памяти до того, как можно читать регистры. Использование MSI позволяет избежать этой проблемы, так как запись в память, генерирующая прерывание, не может обогнать запись данных, поэтому, когда возникло прерывание, драйвер точно знает, что запись данных уже завершена.

PCI устройства могут поддерживать только одно классическое прерывание на функцию. Зачастую, драйвер вынужден опрашивать устройства, что бы определить, какое именно событие произошло, что в общем случае, замедляет обработку прерываний. Использование MSI дает возможность устройствам поддерживать большее количество прерываний, что позволяет каждому прерыванию придать своё назначение. Возможно так спроектировать драйвер, что нечастые прерывания (например - ошибки) будут обрабатываться своим собственным обработчиком, что позволит более эффективно реализовать путь обработки нормальных прерываний. Можно спроектировать драйвер так, что на каждую отдельную очередь пакетов сетевой карточки будет обслуживать собственный обработчик прерываний, или собственное прерывание будет у каждого порта устройства хранения данных.

4. Как использовать MSI ?

Во время аппаратной инициализации, PCI устройства настраиваются на использование классических прерываний. Переключить устройство на использование MSI или MSI-X должен драйвер устройства. Не все архитектуры поддерживают MSI и на таких машинах, вызов API функций, описанных ниже, завершится ошибкой и устройство продолжит работать в классическом режиме линий прерывания.

4.1 Включаем поддержку ядра для MSI

Для того, что бы ядро Linux поддерживало MSI, необходимо собрать его с включённой опцией CONFIG_PCI_MSI. Эта опция доступна не на всех архитектурах и она может зависеть от некоторых других опций, которые тоже должны быть установлены. Например, для х86 необходимо включить опцию X86_UP_APIC или SMP для того, что бы сделать доступной опцию CONFIG_PCI_MSI.

4.2 Используем MSI

Самая тяжёлая часть работы для драйвера делается PCI-уровнем ядра. Он должен просто запросить PCI-уровень ядра установить поддержку возможностей MSI для данного устройства.

4.2.1 pci_enable_msi


int pci_enable_msi(struct pci_dev *dev)

Успешный вызов этой функции выделит ОДНО прерывание устройству, независимо от того, как много MSI способно обслуживать устройство. Устройство при этом будет переключено в режим MSI. Поле dev->irq, хранящее номер прерывания, будет изменено на новый номер, который будет предоставлен MSI. Эта функция должна быт вызвана до того, как драйвер вызовет request_irq() так как включение MSI запрещает линии IRQ и драйвер не получит прерываний от старых прерываний.

4.2.2 pci_enable_msi_block


int pci_enable_msi_block(struct pci_dev *dev, int count)

Этот вариант вызова, описанного выше, позволяет драйверу устройства запросить множество MSI. Спецификация MSI позволяет выделять прерывания в количестве, равном степени двойки, до 2^5 = 32.

Если эта функция вернула 0, то это означает успешное выделение, как минимум, столько прерываний, сколько запросил драйвер. На самом деле, их может быть выделено больше, для того, что бы удовлетворить требованию "степень двойки". В этом случае, функция разрешает MSI на этом устройстве и устанавливает поле dev->irq в первый номер прерывания, выделенный устройству. Все остальные прерывания, назначенные устройству, лежат в диапазоне от dev->irq до dev->irq + count - 1.

Если эта функция вернула отрицательное число, то это указывает на ошибку и драйвер не должен пытаться запрашивать дополнительные MSI прерывания для данного устройства. Если функция вернула положительное значение, то это значение будет меньше, чем count и оно указывает реальное количество прерываний, которые были выделены устройству. В любом случае, значение irq не изменится, а само устройство не будет переключено в режим MSI.

Драйвер устройства должен решить, что делать, в случае, если pci_enable_msi_block() вернула значение, меньшее, чем запрошенный count. Некоторые устройства могут использовать меньше прерываний, чем тот максимум, который они запросили; в этом случае драйвер должен вновь вызвать pci_enable_msi_block(). Следует отметить, что успех не гарантируется, даже если значение count уменьшено до значения. которое вернул предыдущий вызов pci_enable_msi_block(). Это возможно потому, что существует множество ограничений на количество векторов прерываний, которое может выделить система; pci_enable_msi_block() будет возвращать такое значение до тех пор, пока некоторые ограничения не позволяют успешно завершить вызов.

4.2.3 pci_disable_msi


void pci_disable_msi(struct pci_dev *dev)

Эта функция используется для отмены действия pci_enable_msi() или pci_enable_msi_block(). Её вызов восстанавливает значение dev->irq записывая туда номер IRQ и освобождает ранее выделенные MSI. В дальнейшем эти прерывания могут быть назначены другому устройству, поэтому драйвер не должен запоминать это эти значения.

Драйвер устройства обязательно должен вызывать free_irq() для тех прерываний, для которых он вызывал request_irq() ДО вызова этой функции. При неудаче устройство останется в режиме MSI и его вектора будут потеряны. Будет выполнен отладочный вызов BUG_ON().

4.3 Использование MSI-X

MCI-X предоставляет намного более гибкие возможности, чем MSI. Она поддерживает до 2048 прерываний, каждое из которых может управляться независимо от других. Для поддержания такой гибкости, драйвер должен использовать массив структур struct msix_entry :


struct msix_entry {
	u16 	vector; /* kernel uses to write alloc vector */
	u16	entry; /* driver uses to specify entry */
};


Это позволяет драйверу использовать разряженные номера прерываний. Например, он может использовать номера 3 и 1027 и выделить место всего под двухэлементный массив. Предполагается, что драйвер заполнит поля entry каждого элемента массива, значениями указывающими, какие номера прерываний он желает что бы ядро выделило для устройства. Ошибкой будет указание двух одинаковых значений поля entry в разных элементах массива.

4.3.1 pci_enable_msix


int pci_enable_msix(struct pci_dev *dev, struct msix_entry *entries, int nvec)

Вызов этой функции запрашивает у PCI-подсистемы ядра выделить nvec MSI прерываний. Аргумент entries указывает на массив структур типа msix_entry который должен имет размер не менее nvec. При успешном завершении функция вернет 0 и устройство будет переключено в режим MSI-X прерываний. В каждом элементе массива структур, поле vector получит соответствующий номер прерывания. После этого драйвер должен вызвать request_irq() для каждого значения vector, которое он собирается использовать.

Если эта функция вернула отрицательное значение, то это указывает на ошибку и драйвер не должен больше пытаться выделять MSI-X прерывания для данного устройства. Если он вернул положительное число, то оно указывает на максимальный номер вектора прерывания, который был выделен системой. См. пример ниже.

В противоположность pci_enable_msi() эта функция НЕ настраивает dev->irq. Устройство перестанет генерировать прерывания по этому номеру прерывания, при включении режима MSI-X. Драйвер устройства отвечает за отслеживание прерываний, назначенных MSI-X векторам для того, что бы он мог, в последствии, освободить их позднее.

Во время фазы инициализации, драйвер устройства должен вызвать эту функцию однократно для каждого устройства.

Идеально будет, если драйвер способен работать с разными номерами MSI-X прерываний, так есть много причин, по которым разные платформы окажутся не в состоянии выделить именно те номера, которые запросил драйвер.

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

static int foo_driver_enable_msix(struct foo_adapter *adapter, int nvec)
{
	while (nvec >= FOO_DRIVER_MINIMUM_NVEC) {
		rc = pci_enable_msix(adapter->pdev,
				     adapter->msix_entries, nvec);
		if (rc > 0)
			nvec = rc;
		else
			return rc;
	}

	return -ENOSPC;
}


4.3.2 pci_disable_msix


void pci_disable_msix(struct pci_dev *dev)

Эта функция API должна использоваться для отмены результатов действия pci_enable_msix(). Она осовбождает ранее выделенные MSI прерывания. В последствии, эти прерывания могут быть назначены другому устройству, поэтому драйвер не должен хранить и использовать значения поля vector после вызова pci_disable_msix().

Драйвер устройства обязательно должен вызывать free_irq() для всех прерываний, для которых он делал вызов request_irq(), перед вызовом этой функции. Иначе возникнет ошибка, результатом которой будет отладочная печать в BUG_ON(), устройство выйдет из режима MSI и вектор будет потерян.

4.3.3 Таблица MSI-X

В элементе MSI-X capability указывается BAR и смещение для указания на таблицу MSI-X. Этот адрес устанавливается PCI подсистемой ядра и не должен использоваться напрямую драйвером устройства. Если драйвер намеревается маскировать / размаскировать некотрое прерывание, то он должен делать это вызывая disable_irq() / enable_irq().

4.4 Управление устройствами, в которых реализовано как MSI так и MSI-X

Если устройство реализует оба механизма - как MSI так и MSI-X, то на нем может быть запущен любой из этих режимов, но не оба вместе. Это требование спецификации PCI, которое обусловлено PCI уровнем ядра. Вызов pci_enable_msi() когда уже активен PCI-X или вызов pci_enable_msix() в то время, когда уже активен MSI, будет иметь результатом ошибку. Если драйвер устройства желает переключиться между режимами MSI / MCI-X он должен, прежде всего, остановить устройство, затем переключиться в традиционный режим линий IRQ, выполнить вызов pci_enable_msi() / pci_enable_msix() и только после этого продолжить работу. Не предполагается, что это - распространённая операция, но она может быть полезна для целей отладки и тестирования во время разработки.

4.5 Когда стоит использовать MSI ?

4.5.1 Выбор между MSI-X и MSI

Если Ваше устройство поддерживает и MSI и MSI-X, предпочтительно, что бы Вы использовали возможности MSI-X. Как упоминалось выше, MSI-X поддерживает любое количество номеров прерываний от 1 до 2048. В то время, как MSI ограничен 32 прерываниями. Кроме того, так как вектора прерываний MSI должны быть расположены последовательно, система, возможно, окажется не в состоянии выделить так много векторов MSI, как для MSI-X. На некоторых платформах, все MSI прерывания передаются на один набор процессоров, тогда как все прерывания MSI-X могут быть нацелены на различные ЦП.

4.5.2 Spinlocks

Большинство драйверов устройств имеют спинлоки, связанные с каждым устройством, которые захватываются обработчиком прерываний. При работе с линиями прерывания или единственным прерыванием MSI, нет необходимости запрещать прерывания (ядро Linux гарантирует, что одно и тоже прерывание не будет повторно входимым). Если же устройство работает с множеством прерываний, то драйвер должен запретить прерывания пока сохраняется блокировка. Если устройство пошлет другое прерывание, драйвер войдет в клинч, пытаясь рекурсивно получить спинлок.

Есть два решения. Первое заключается в использовании вызовов функций API spin_lock_irqsave() или spin_lock_irq() (Смотри Documentation/DocBook/kernel-locking). Второе заключается в использовании флага IRQF_DISABLED при вызове request_irq(), так что ядро будет запускать обработчик прерываний при запрещённых прерываниях.

Если Ваш обработчик MSI прерываний не захватывает блокировку на все время, пока он работает, первое решение является более предпочтительным. Второе решение обычно предпочитается тогда, когда нужно избежать двух переходов из состояния запрета прерывания в состояние разрешения и обратно.

4.6 Как узнать, поддерживает ли устройство MSI/MSI-X ?

Использование 'lspci -v' (как root) может показать на некоторых устройствах возможности "MSI", "Message Signalled Interrupts" или "MSI-X". Each of these capabilities has an 'Enable' flag which will be followed with either "+" (enabled) or "-" (disabled). Каждая из этих возможностей имеет флаг 'Enable', который следует за ней и выглядит как "+" (включено) or "-" (выключено).

5.Проблемы MSI

Некоторые чипсеты PCI или устройства, как известно, не поддерживают MSI. Стек PCI предоставляет три метода для запрета MSI :

  1. Глобально
  2. На всех устройствах за конкретным мостом
  3. На единственном устройстве

5.1. Глобальный запрет MSI

Некоторые чипсеты не поддерживают совсем или поддерживают MSI неправильно. Если Вам повезет, то производитель, знающий об этом, отметит это в таблице ACPI FADT. В этом случае, ядро Linux автоматически запретит MSI. Некоторые платы не включают эту информацию в таблицу и тогда мы должны определять это самостоятельно. Наиболее полный список таких плат можно найти рядом с функцией quirk_disable_all_msi() в файле drivers/pci/quirks.c.

Если Ваша плата имеет проблемы с MSI, Вы можете передать команду pci=nomsi в командной строке загрузки ядра, для запрета MSI на всех устройствах. В Ваших интересах сообщить о проблеме в linux-pci@vger.kernel.org приложив полную выдачу команды lspci -v что бы мы могли присоединить эту проблему к ядру.

5.2. Запрет MSI ниже моста

Некоторые мосты не способны правильно направлять MSI между ветвями. В этом случае необходимо запретить MSI для всех устройств за этим мостом.

Некоторые мосты позволяют Вам включать MSI путём изменения некоторых бит в их конфигурационном пространстве PCI (Особенно - чипсеты Hypertransport такие, как nVidia nForce и Serverworks HT2000). Ядро Linux, главным образом, знает об этих проблемах и автоматически разрешает MSI, если это возможно. Если у Вас мост, о котором ядро Linux ничего не знает, Вы можете включить MSI в конфигурационном пространстве, используя любой метод, про который Вы знаете, что он работает, а затем включить MSI на мосте, следующим образом :


 echo 1 > /sys/bus/pci/devices/$bridge/msi_bus

где $bridge - PCI адрес моста, который Вы включаете (eg 0000:00:0e.0).

Для запрета MSI выдайте команду echo 0 вместо 1. Выполнять такие действия необходимо с осторожностью, так как они могут нарушить обработку прерываний у всех устройств, расположенных ниже моста.

Повторяю, пожалуйста, сообщите о всех мостах, которым требуется специальное управление, по адресу linux-pci@vger.kernel.org.

5.3. Запрет MSI на одном устройстве

Известно, что на некоторых устройствах MSI реализован с ошибками. Обычно, такие устройства управляются индивидуальными драйверами, но иногда необходимо управлять ими с помощью различных уловок. Некоторые драйверы имеют опцию для запрета MSI. Хотя это является простейшим выходом для разработчика драйвера, нельзя признать это хорошей идеей и не следует подражать такому решению.

5.4. Определение причины запрета MSI на устройстве

Из трёх предыдущих разделов Вы могли увидеть, что есть множество причин, по которым MSI отключен на данном устройстве. Вашим первым шагом должна быть внимательная проверка файла dmesg для выяснения того, включены ли MSI на Вашей машине. Кроме того, Вам необходимо проверить файл .config и убедится, что опция CONFIG_PCI_MSI включена.

После этого, выполнив команду 'lspci -t' Вы получите список мостов, расположенных выше Вашего устройства. Просмотр содержимого файла /sys/bus/pci/devices/*/msi_bus скажет Вам, включен ли (1) или выключен (0) MSI. Если Вы обнаружите 0 в файле msi_bus относящемуся к мосту корнем PCI и устройством, то MSI будет запрещен для всей ветви.

Полезно так же проверить сам драйвер, что бы выяснить, поддерживает ли он MSI. Такой драйвер должен содержать вызовы функций pci_enable_msi(), pci_enable_msix() or pci_enable_msi_block().