Как ядро ​​Linux обрабатывает общие IRQ?


14

Согласно тому, что я прочитал до сих пор, «когда ядро ​​получает прерывание, все зарегистрированные обработчики вызываются».

Я понимаю, что зарегистрированные обработчики для каждого IRQ могут просматриваться через /proc/interrupts, и я также понимаю, что зарегистрированные обработчики происходят от драйверов, которые вызвали request_irqпередачу обратного вызова примерно в форме:

irqreturn_t (*handler)(int, void *)

Исходя из того, что я знаю, должен быть вызван каждый из этих обратных вызовов обработчика прерываний, связанных с конкретным IRQ, и обработчик должен определить, действительно ли оно должно обрабатывать прерывание. Если обработчик не должен обрабатывать конкретное прерывание, он должен вернуть макрос ядра IRQ_NONE.

Что мне трудно понять, так это то, как каждый драйвер должен определять, должен ли он обрабатывать прерывание или нет. Я предполагаю, что они могут отслеживать внутренне, если они ожидают прерывания. Если это так, я не знаю, как они могли бы справиться с ситуацией, в которой несколько водителей за одним IRQ ожидают прерывания.

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

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

Редактировать: В случае, если это актуально, рассматриваемая архитектура процессора x86.


1
stackoverflow.com/questions/14371513/for-a-shared-interrupt-line-how-do-i-find-which-interrupt-handler-to-usec
Сиро Сантилли 新疆 改造 中心 法轮功 六四 事件

Ответы:


14

Это описано в главе 10 « Драйверы устройств Linux» , 3-е издание, Corbet et al. Он доступен бесплатно онлайн , или вы можете отказаться от шекелей О'Рейли за формы мертвых деревьев или электронных книг. Часть, относящаяся к вашему вопросу, начинается на странице 278 в первой ссылке.

Для чего это стоит, вот моя попытка перефразировать эти три страницы, а также другие биты, которые я гуглил:

  • Когда вы регистрируете общий обработчик IRQ, ядро ​​проверяет, что либо:

    а. другого обработчика для этого прерывания не существует, или

    б. все ранее зарегистрированные также запрашивали обмен прерываниями

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

  • Когда аппаратное устройство PCI¹ поднимает линию IRQ, вызывается низкоуровневый обработчик прерываний ядра, который, в свою очередь, вызывает все зарегистрированные обработчики прерываний, передавая каждый назад тот, который dev_idвы использовали для регистрации обработчика request_irq().

    dev_idЗначение должно быть машинно-уникальным. Обычный способ сделать это - передать указатель на устройство, structиспользуемое вашим драйвером для управления этим устройством. Поскольку этот указатель должен находиться в области памяти вашего драйвера, чтобы он был полезным для драйвера, он ipso facto уникален для этого драйвера.

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

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

    Пример Corbet et al. дать это параллельный порт ПК. Когда он утверждает строку прерывания, он также устанавливает старший бит в своем первом регистре устройства. (То есть inb(0x378) & 0x80 == trueпри условии стандартной нумерации портов ввода / вывода.) Когда ваш обработчик обнаружит это, он должен выполнить свою работу, а затем очистить IRQ, записав значение, считанное из порта ввода / вывода, обратно в порт с верхним немного очищен.

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

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

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

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


Примечания:

  1. Я указывал PCI выше, потому что все вышеперечисленное предполагает прерывания, инициируемые уровнем , как использовалось в оригинальной спецификации PCI. В ISA использовались прерывания, инициируемые фронтом , что делало совместное использование в лучшем случае хитрым, и возможно даже тогда, только когда поддерживается аппаратным обеспечением. PCIe использует сообщения с прерываниями; сообщение о прерывании содержит уникальное значение, которое ядро ​​может использовать, чтобы избежать круговой игры, предполагающей совместное использование прерываний PCI. PCIe может устранить саму необходимость разделения прерываний. (Я не знаю, так ли это на самом деле, просто у этого есть потенциал.)

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


1
Как вы упомянули, обработчик прерываний может быть передан, dev_idкоторый ему не принадлежит. Мне кажется, что существует ненулевая вероятность того, что драйвер, который не владеет dev_idструктурой, все равно может принять ее за свою собственную, основываясь на том, как он интерпретирует содержимое. Если это не так, то какой механизм предотвратит это?
Bsirang

Вы предотвращаете это, делая dev_idуказатель на что-то в области памяти вашего драйвера. Другой водитель может составить dev_idзначение , которое случилось быть начертанием с указателем на память драйвер владеет, но это не будет происходить , потому что все играют по правилам. Помните, это пространство ядра: самодисциплина подразумевается как нечто само собой разумеющееся, в отличие от кода пользовательского пространства, который может безболезненно предполагать, что все, что не запрещено, разрешено.
Уоррен Янг

Согласно десятой главе LDD3: «Всякий раз, когда два или более драйверов совместно используют линию прерываний, и аппаратное обеспечение прерывает процессор в этой строке, ядро ​​вызывает каждый обработчик, зарегистрированный для этого прерывания, передавая каждому свой собственный dev_id». Это похоже на предыдущее понимание был неверен относительно того, может ли обработчик прерывания быть передан в то, dev_idчто ему не принадлежит.
bsirang

Это было неправильно прочитано с моей стороны. Когда я писал это, я объединял два понятия. Я отредактировал свой ответ. Условие, которое требует от обработчика прерываний быстрого возврата, заключается в том, что он вызывается из-за утверждения прерывания устройством, которым он не управляет. Значение dev_idне помогает вам определить, произошло ли это. Вы должны спросить аппаратное обеспечение, "Вы звонили?"
Уоррен Янг

Да, теперь мне нужно выяснить, как я на самом деле заставляю других драйверов думать, что их устройства «зазвонили» после перезагрузки ядра через kexec.
Бсиранг

4

Когда драйвер запрашивает общий IRQ, он передает указатель ядру на ссылку на специфическую для устройства структуру в области памяти драйвера.

Согласно LDD3:

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

После проверки обработчиков IRQ нескольких драйверов выясняется, что они проверяют само оборудование, чтобы определить, должно ли оно обрабатывать прерывание или возврат IRQ_NONE.

Примеры

UHCI-HCD Драйвер
  status = inw(uhci->io_addr + USBSTS);
  if (!(status & ~USBSTS_HCH))  /* shared interrupt, not mine */
    return IRQ_NONE;

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

SDHCI Драйвер
  intmask = sdhci_readl(host, SDHCI_INT_STATUS);

  if (!intmask || intmask == 0xffffffff) {
    result = IRQ_NONE;
    goto out;
  }

Как и в предыдущем примере, драйвер проверяет регистр состояния, SDHCI_INT_STATUSчтобы определить, нужно ли ему обслуживать прерывание.

Ath5k Driver
  struct ath5k_softc *sc = dev_id;
  struct ath5k_hw *ah = sc->ah;
  enum ath5k_int status;
  unsigned int counter = 1000;

  if (unlikely(test_bit(ATH_STAT_INVALID, sc->status) ||
        !ath5k_hw_is_intr_pending(ah)))
    return IRQ_NONE;

Еще один пример.


0

Пожалуйста, посетите проверить эту ссылку :

Обычная практика - запускать нижние половины или любую другую логику в обработчике IRQ только после проверки состояния IRQ из отображенного в памяти регистра. Следовательно, проблема по умолчанию решена хорошим программистом.


Содержание вашей ссылки недоступно
user3405291
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.