Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: PCIe Driver
Форум разработчиков электроники ELECTRONIX.ru > Cистемный уровень проектирования > Операционные системы > Linux
doom13
Приветствую.
Есть плата с Virtex 7, надо забросить данные в ПК по PCIe. Ранее такого не делал и есь трудности.
В FPGA собрана система: GPIO, Timer, PCIe Bridge (регистры GPIO и таймера смапированы на адресное пространство BAR0). Разбираюсь в написании модулей ядра Linux (использую Ubuntu 15.04).
Пока реализовал управление GPIO при загрузке/выгрузке модуля в Linux. Хочу добавить прерывание. В FPGA линия interrupt от таймера заводится на PCIe-мост, он должен запихнуть его в систему.
Для настройки прерывания в драйвере использую:
Код
irq_handler_t timer_isr(unsigned int irq, void *dev);
struct pci_dev *pdev;
char irq;
...
pci_read_config_byte( pdev, PCI_INTERRUPT_LINE, &irq ); // для моей платы получаю irq = 11
request_irq(irq_num, timer_isr, IRQF_TRIGGER_RISING, "PCIe INT", (void *)pdev);
...


Функция pci_read_config_byte() установит irq = 11.

Далее настраиваю таймер, чтобы генерировал прерывание и стартую его. В syslog вижу следующее сообщение
Цитата
Jul 29 10:57:49 user-pc kernel: [ 797.188154] irq 16: nobody cared (try booting with the "irqpoll" option)
Jul 29 10:57:49 user-pc kernel: [ 797.188158] CPU: 7 PID: 0 Comm: swapper/7 Tainted: G OE 3.19.0-25-generic #26-Ubuntu
Jul 29 10:57:49 user-pc kernel: [ 797.188159] Hardware name: Gigabyte Technology Co., Ltd. To be filled by O.E.M./Z77P-D3, BIOS F7 08/24/2012
Jul 29 10:57:49 user-pc kernel: [ 797.188160] ffff880408dc92a4 ffff88041edc3e28 ffffffff817c4518 0000000000040400
Jul 29 10:57:49 user-pc kernel: [ 797.188162] ffff880408dc9200 ffff88041edc3e58 ffffffff810d0e86 ffff88041edc3e88
Jul 29 10:57:49 user-pc kernel: [ 797.188164] ffff880408dc9200 0000000000000000 0000000000000010 ffff88041edc3e98
Jul 29 10:57:49 user-pc kernel: [ 797.188165] Call Trace:
Jul 29 10:57:49 user-pc kernel: [ 797.188166] <IRQ> [<ffffffff817c4518>] dump_stack+0x45/0x57
Jul 29 10:57:49 user-pc kernel: [ 797.188174] [<ffffffff810d0e86>] __report_bad_irq+0x36/0xd0
Jul 29 10:57:49 user-pc kernel: [ 797.188175] [<ffffffff810d1237>] note_interrupt+0x267/0x2b0
Jul 29 10:57:49 user-pc kernel: [ 797.188177] [<ffffffff810ce8c3>] handle_irq_event_percpu+0x133/0x1a0
Jul 29 10:57:49 user-pc kernel: [ 797.188179] [<ffffffff810ce971>] handle_irq_event+0x41/0x70
Jul 29 10:57:49 user-pc kernel: [ 797.188181] [<ffffffff810d1666>] handle_fasteoi_irq+0x86/0x140
Jul 29 10:57:49 user-pc kernel: [ 797.188182] [<ffffffff81017772>] handle_irq+0x22/0x40
Jul 29 10:57:49 user-pc kernel: [ 797.188184] [<ffffffff817ce55f>] do_IRQ+0x4f/0xf0
Jul 29 10:57:49 user-pc kernel: [ 797.188186] [<ffffffff817cc36d>] common_interrupt+0x6d/0x6d
Jul 29 10:57:49 user-pc kernel: [ 797.188186] <EOI> [<ffffffff816663b5>] ? cpuidle_enter_state+0x65/0x160
Jul 29 10:57:49 user-pc kernel: [ 797.188190] [<ffffffff816663a1>] ? cpuidle_enter_state+0x51/0x160
Jul 29 10:57:49 user-pc kernel: [ 797.188192] [<ffffffff81666597>] cpuidle_enter+0x17/0x20
Jul 29 10:57:49 user-pc kernel: [ 797.188195] [<ffffffff810b7ce1>] cpu_startup_entry+0x311/0x3b0
Jul 29 10:57:49 user-pc kernel: [ 797.188198] [<ffffffff81049107>] start_secondary+0x197/0x1c0
Jul 29 10:57:49 user-pc kernel: [ 797.188199] handlers:
Jul 29 10:57:49 user-pc kernel: [ 797.188202] [<ffffffff815bb130>] usb_hcd_irq
Jul 29 10:57:49 user-pc kernel: [ 797.188204] Disabling IRQ #16


Получается PCIe мост при наличии прерывания от таймера выдаёт его на 16 линию. Для просмотра оборудования в системе установлен System Profiler, для моего устройства он показывает IRQ = 16 (рисунок).
Если пытаюсь использовать 16 линию прерывания в функции request_irq, она выдаёт ошибку.
Код
request_irq(16, timer_isr, IRQF_TRIGGER_RISING, "PCIe INT", (void *)pdev);

Вопрос, что я делаю не так? Почему в конфигурационной области моего устройства записано 11 (Interrupt Line), а прерывание срабатывает на 16 линии?

Код модуля:
CODE

/* Necessary includes for device drivers */
#include <linux/init.h>
//#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/pci.h>
#include <linux/pci_regs.h>
#include <linux/interrupt.h>
//#include <asm/system.h> /* cli(), *_flags */
#include <asm/uaccess.h> /* copy_from/to_user */

#include "hardware.h"
#include "timer.h"

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Andrei Hres");
MODULE_DESCRIPTION("Driver for /dev/virtex7_pcie");
MODULE_SUPPORTED_DEVICE("virtex7_pcie");

#define __DEBUG_MODE
#define DRV_NAME "virtex7_pcie"

#define BAR0 0
#define BAR1 1
#define BAR2 2
#define BAR3 3
#define BAR4 4
#define BAR5 5

int virtex7_pcie_init(void);
void virtex7_pcie_exit(void);
int virtex7_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *ent);
void virtex7_pcie_remove(struct pci_dev *pdev);

irq_handler_t timer_isr(unsigned int irq, void *dev);
int irq_status;
char irq_num;

char *bar0_base;
unsigned long *pio_base;

static struct pci_device_id virtex7_pcie_ids[] = { {PCI_DEVICE(0x10EE, 0x7014)}, {0,} };

MODULE_DEVICE_TABLE(pci, virtex7_pcie_ids);

static struct pci_driver virtex7_pcie_driver = {
.name = DRV_NAME,
.probe = virtex7_pcie_probe,
.remove = virtex7_pcie_remove,
.id_table = virtex7_pcie_ids,
//#ifdef CONFIG_PM
// .suspend = pcie_suspend,
// .resume = pcie_resume,
//#endif /* CONFIG_PM */
};

//static int __init virtex7_pcie_init(void)
int virtex7_pcie_init(void)
{
printk(KERN_ALERT "*****************************************\n");
printk(KERN_ALERT "Inserting PCIe module\n");
return pci_register_driver(&virtex7_pcie_driver);
}

//static void __exit virtex7_pcie_exit(void)
void virtex7_pcie_exit(void)
{
printk(KERN_ALERT "Removing PCIe module\n");
pci_unregister_driver(&virtex7_pcie_driver);
printk(KERN_ALERT "*****************************************\n");
}

int virtex7_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
int status;
int st;
unsigned long data;
char byte2;
unsigned long bar0_start;
unsigned long bar0_end;
unsigned long *pio_reg;

printk(KERN_ALERT "Entering PCIe probe() function\n");

status = pci_enable_device(pdev);

if(status == 0)
{
printk(KERN_ALERT "PCIe device is succesfully enabled\n");
printk(KERN_ALERT "vendor: 0x%.4X\n", pdev->vendor);
printk(KERN_ALERT "device: 0x%.4X\n", pdev->device);

pci_read_config_dword(pdev, 0, &data);
printk(KERN_ALERT "Data from config space: 0x%.8X\n", data);
pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &irq_num);
printk(KERN_ALERT " PCI_INTERRUPT_LINE=%d\n", irq_num);

//irq_num = 16;

pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &byte2);
printk(KERN_ALERT " PCI_INTERRUPT_PIN=%d\n", byte2);

bar0_start = pci_resource_start(pdev, BAR0);
bar0_end = pci_resource_end(pdev, BAR0);
printk(KERN_ALERT "BAR0 start address: 0x%.8X\n", bar0_start);
printk(KERN_ALERT "BAR0 end address: 0x%.8X\n", bar0_end);
printk(KERN_ALERT "BAR0 size: %u bytes = %u kB\n", bar0_end-bar0_start, (bar0_end-bar0_start+1)/1024);

//pio_reg = (unsigned long *) (bar0_start + 0x8000);
//*pio_reg = 0xFFFFFFFF;

//----------------------------------------------------------------

st = pci_request_region(pdev, BAR0, "virtex7_pcie_bar0");
if(st == 0)
{
printk(KERN_ALERT "Request region success\n");
bar0_base = pci_iomap(pdev, BAR0, 0);
printk(KERN_ALERT "bar0_base: 0x%.16X\n", (unsigned int) bar0_base);

pio_base = (unsigned long *) (bar0_base + 32768);
printk(KERN_ALERT "pio_base: 0x%.16X\n", (unsigned int) pio_base);

//*(unsigned long *)(bar0_base + 32768) = 0x1;
*pio_base = 0x1;

Timer_Init(bar0_base + TIMER_0_OFFSET_BYTES);
Timer_IntClear(bar0_base + TIMER_0_OFFSET_BYTES);
data = Timer_DbgRdCSR(bar0_base + TIMER_0_OFFSET_BYTES);
printk(KERN_ALERT "Timer CSR: 0x%.8X\n", (unsigned int) data);

// free_irq(irq_num, (void *)pdev);
irq_status = request_irq(irq_num, timer_isr, IRQF_TRIGGER_RISING/* | IRQF_SHARED*/, "PCIe INT", (void *)pdev);
if(irq_status)
printk(KERN_ALERT "request_irq() error\n");
else
{
printk(KERN_ALERT "request_irq() success\n");
Timer_Start(bar0_base + TIMER_0_OFFSET_BYTES);
}

Timer_IntClear(bar0_base + TIMER_0_OFFSET_BYTES);
data = Timer_DbgRdCSR(bar0_base + TIMER_0_OFFSET_BYTES);
printk(KERN_ALERT "Timer CSR: 0x%.8X\n", (unsigned int) data);
}

//pio_base = (unsigned char *) (addr_bar0 + 0x8000);
//----------------------------------------------------------------
}

return status;
}

void virtex7_pcie_remove(struct pci_dev *pdev)
{
*pio_base = 0;


printk(KERN_ALERT "Disable PCIe device\n");
pci_disable_device(pdev);
pci_release_region(pdev, BAR0);

if(irq_status == 0)
{
free_irq(irq_num, (void *)pdev);
Timer_Stop(bar0_base + TIMER_0_OFFSET_BYTES);
printk(KERN_ALERT "Free PCI interrupt line #%d\n", irq_num);
}
}

module_init(virtex7_pcie_init);
module_exit(virtex7_pcie_exit);

irq_handler_t timer_isr(unsigned int irq, void *dev)
{
static int int_counter = 0;

if(Timer_IntStatus(bar0_base + TIMER_0_OFFSET_BYTES))
{
Timer_IntClear(bar0_base + TIMER_0_OFFSET_BYTES);
}

printk(KERN_ALERT "Interrupt counter %d\n", int_counter);
int_counter++;

return IRQ_NONE;
}

doom13
Прерывание стало работать со следующими изменениями:
Код
request_irq(pdev->irq, timer_isr, IRQF_SHARED, "PCIe INT", (void *)pdev); // pdev->irq = 16

, ранее использовался неверный флаг IRQF_TRIGGER_RISING и значение pdev->irq = 16.
Остался вопрос по номеру линии прерывания. В конфигурационной области PCIe моего устройства записано 11 (Interrupt Line), а прерывание срабатывает для линии №16.
Нашёл, что при старте системы плате назначена линия прерывания №11, через некоторое время линию прерывания №11 занимает другое устройство (SMBus Controller). Плате назначается линия прерывания №16, которую она делит с USB Controller-ом, но номер линии прерывания (Interrupt Line) в конфигурационной области PCIe не меняется (может и меняется но функция pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &irq )устанавливает переменную irq = 11)?

В struct pci_dev *pdev значение pdev->irq = 16, а если использовать pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &irq) получим irq = 11???
doom13
Предположил, что раз система видит устройство как PCI Mouse Controller, то она и вешает его на туже линию прерывания, что и USB Controller.
Поменял настройки для PCIe Bridge (теперь класс устройства стал Base system peripherals), но результат одинаков (при старте системы повесило на 11 линию прерывания, после запуска модуля перекинуло на 16).
Как сделать так, чтоб моему устройству назначалась отдельная линия прерывания (в системе есть много свободных)?

Для мультимедиа-девайс всё аналогично.
Tarbal
Насколько я понимаю вопрос. PCI имеет механизмы обработки прерываний даже если несколько устройств пользуется одним и тем же проводом для вызова прерывания. Много устройств прекрасно ладят используя общий провод для вызова прерываний.

Далее пойдут общие соображения и я не поручусь, что они точны, поскольку с PCI мало работал.
Прерывание физически присоединено к проводу и задется при изготовлении устройства. Можно изготовить устройство так, что есть возможность выбора. Надо найти документацию на устройство и узнать если есть такая возможность.
doom13
Цитата(Tarbal @ Jul 31 2015, 14:45) *
Насколько я понимаю вопрос. PCI имеет механизмы обработки прерываний даже если несколько устройств пользуется одним и тем же проводом для вызова прерывания. Много устройств прекрасно ладят используя общий провод для вызова прерываний.

Да, так и есть - моё PCIe устройство делит Interrupt Line #16 c USB контроллером (при определении обработчика прерывания используется флаг IRQF_SHARED).
Но в системе куча свободных линий и хотелось бы повесить его на отдельную.

Цитата(Tarbal @ Jul 31 2015, 14:45) *
Далее пойдут общие соображения и я не поручусь, что они точны, поскольку с PCI мало работал.
Прерывание физически присоединено к проводу и задется при изготовлении устройства. Можно изготовить устройство так, что есть возможность выбора. Надо найти документацию на устройство и узнать если есть такая возможность.

А как тогда объяснить что при старте система назначает моему устройству Interrupt Line #11, а потом (загружаю свой модуль в ядро) перебрасывает его на Interrupt Line #16? Как я пока понял прерывания для PCIe работают на уровне Transaction Layer, т.е. устройство PCIe передаёт сигнал прерывания посредством сообщений.
Не совсем понимаю разницу между Legacy Interrupt, MSI Interrupt и MSI-X Interrupt, но вроде как речь везде идёт о транзакциях.
Посмотрю, что там может быть ещё железного.
Спасибо.


Раньше драйвер немного неправильно конфигурировал таймер - прерывание срабатывало очень часто. Мышь, клава и всё остальное, что висело на USB контроллере с Interrupt Line #16, жутко тормозили. Счас подправил функции настройки таймера и он отлично делит 16 линию прерывания с USB контроллером.
Вопрос, как происходит назначение устройству линии прерывания, остаётся актуальным. Буду благодарен, если кто подскажет.
doom13
Попробовал поменять местами видеокарту и мою плату с Virtex 7, номера линий прерываний назначаемые устройствам сохранились (у видеокарты было и осталось Interrupt Line 30, для моей платы - 16)???
doom13
Как правильно со стороны ядра выделить память, чтобы моё устройство могло писать в неё?

Пока сделал так:
Код
pc_buffer = kmalloc(pc_buffer_size, GFP_KERNEL); // выделяю буфер
pc_buffer_phys = virt_to_phys(pc_buffer); // получаю физический адрес выделенной памяти


Полученный физический адрес памяти (32 бита) записываю в регистр PCIe моста в Virtex 7 (старшие 32 бита записываю 0 т.к. возвращаемый адрес 32 бита). Пытаюсь из Xilinx SDK получить доступ к выделенному адресному пространству (через мост). Иногда всё проходит нормально, вижу память выделенную драйвером Linux (предварительно установил все байты буфера в определённое значение). Могу изменить из Xilinx SDK данные в выделенном буфере (в прерывании драйвер мониторит содержимое участка памяти буфера, и могу видеть, что данные меняются). Иногда всё работает как-то криво, либо вообще не могу видеть буфер памяти со стороны FPGA, либо оно подключилось к какому-то другому участку памяти (через мост пишу/читаю данные, т.е. какую-то память он видит, но даннае в буфере драйвера не меняются).
Правильно ли я определяю физический адрес выделенной памяти?
doom13
Последний вопрос снят. Посмотрел старую инфу, где
Код
unsigned long virt_to_phys( volatile void *address );

В новых исходниках
Код
#ifdef CONFIG_PHYS_ADDR_T_64BIT
typedef u64 phys_addr_t;
#else
typedef u32 phys_addr_t;
#endif

static inline phys_addr_t virt_to_phys(volatile void *address)
{
    return __pa(address);
}

, как итог загонял в мост неверный адрес.

Вопрос про прерывания остаётся актуальным, пока так и не понял, как система раздаёт линии прерываний. Добавил только разрешение MSI и система при загрузке модуля стала выделять отдельную линию для моего устройства.
gerber
Система не может взять и выделить PCI-устройству любую линию прерывания, какую посчитает нужным, так как эта линия может схемотехнически тупо не проложена до процессора и аппаратных средств довести эту линию от PCI-устройства до процессора у данной модели материнской платы нет.
Как разложены линии прерывания по плате знает только разработчик платы и сообщает эту информацию разработчику BIOS, который в свою очередь строит в BIOS таблицы роутинга для всех слотов PCI. В процессе старта и энумерации всех подключенных PCI-устройств BIOS раздаёт прерывания и прокладывает их до процессора известным ему аппаратным способом, после чего номер выделенного прерывания записывается в соответствующий конфиг-регистр. Этот регистр просто ячейка памяти для драйвера, который будет работать с устройством, то бишь произвольная перезапись номера прерывания не меняет выделенной линии и просто ведет к сбою логики работы драйвера.
Операционная система может оставить роутинг PCI-прерываний, как его создал BIOS при загрузке, а может и перестроить по-своему, но опираясь на таблицы роутинга, размещённые в BIOS-e.
По практике, поскольку PCI-прерывания работают по уровню и легко разделяются между устройствами, разработчики плат выделяют на PCI 2-3 линии IRQ и всё. Поэтому на уровне физики номер прерывания тот, который записан в регистре устройства и брать для подключения обработчика нужно именно его. Всё остальное, что показывает система - это виртуальные вектора, созданные самой системой в процессе старта драйверов, чтобы как-то разобраться в кипе устройств и отправить пришедшее прерывание IRQ 11 на нужный из 5 подключенных обработчиков.
doom13
На рисунке Configuration Space для устройства PCIe есть поле Interrupt Line. Оно, как понимаю, доступно только для чтения.
Можете пояснить, что это такое и за что оно отвечает?
Если повесить обработчик прерывания на 11 линию (записана в поле Interrupt Line), то работать не будет.
При загрузке модуля указатель на структуру struct pci_dev *pdev содержит номер линии прерывания pdev->irq отличный от того, что хранится в конфигурационной области PCIe (всегда = 11). Это будет либо 16 (MSI запрещены), либо 31 (разрешены MSI). Если обработчик привязан к этому номеру - работает.
Откуда взялся номер 11 в конфигурационной области? Если его пишет система (или BIOS), то почему после переопределения номера линии прерывания он не переписывается на правильный?

Драйвер virtex7.
MSI запрещены
cat /proc/interrupts
CODE

CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7
0: 17 0 0 0 0 0 0 0 IO-APIC-edge timer
1: 2 0 0 0 0 0 0 0 IO-APIC-edge i8042
8: 1 0 0 0 0 0 0 0 IO-APIC-edge rtc0
9: 0 0 0 0 0 0 0 0 IO-APIC-fasteoi acpi
12: 2 0 0 0 0 1 0 1 IO-APIC-edge i8042
16: 11080 968 13363 494207 41182 4940 73084 4109705 IO-APIC 16-fasteoi ehci_hcd:usb3, virtex7
17: 66 176 1 2 11 897 3 17 IO-APIC 17-fasteoi snd_hda_intel
19: 10129 11616 4327 9923 15263 31670 14741 30747 IO-APIC 19-fasteoi ata_piix, ata_piix
23: 27 0 0 7 1 0 0 0 IO-APIC 23-fasteoi ehci_hcd:usb4
25: 0 0 0 0 0 0 0 0 PCI-MSI-edge xhci_hcd
26: 17 87949 1 470 4 0 0 9 PCI-MSI-edge eth0
27: 28196 0 1 42 2 2 0 1 PCI-MSI-edge eth1
28: 12 0 0 0 3 0 0 0 PCI-MSI-edge mei_me
29: 27 103 1 1 11 313 20 7 PCI-MSI-edge snd_hda_intel
30: 1954797 699670 527638 508968 858144 423732 314299 277036 PCI-MSI-edge nouveau
NMI: 68 57 54 49 66 51 38 69 Non-maskable interrupts
LOC: 1477693 1742544 1765876 1910964 513009 525984 531958 2214784 Local timer interrupts
SPU: 0 0 0 0 0 0 0 0 Spurious interrupts
PMI: 68 57 54 49 66 51 38 69 Performance monitoring interrupts
IWI: 4371 467 310 533 505 357 275 360 IRQ work interrupts
RTR: 6 0 0 0 0 0 0 0 APIC ICR read retries
RES: 134654 144649 116848 92117 45669 39876 36313 47002 Rescheduling interrupts
CAL: 52294 87270 93843 89098 75381 80948 91265 91551 Function call interrupts
TLB: 26135 26093 25448 23842 20976 23850 25294 17155 TLB shootdowns
TRM: 0 0 0 0 0 0 0 0 Thermal event interrupts
THR: 0 0 0 0 0 0 0 0 Threshold APIC interrupts
MCE: 0 0 0 0 0 0 0 0 Machine check exceptions
MCP: 50 50 50 50 50 50 50 50 Machine check polls
HYP: 0 0 0 0 0 0 0 0 Hypervisor callback interrupts
ERR: 0
MIS: 0

MSI разрешены
cat /proc/interrupts
CODE

CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7
0: 17 0 0 0 0 0 0 0 IO-APIC-edge timer
1: 2 0 0 0 0 0 0 0 IO-APIC-edge i8042
8: 1 0 0 0 0 0 0 0 IO-APIC-edge rtc0
9: 0 0 0 0 0 0 0 0 IO-APIC-fasteoi acpi
12: 2 0 0 0 0 1 0 1 IO-APIC-edge i8042
16: 11080 968 13363 535687 41182 4940 73084 4587454 IO-APIC 16-fasteoi ehci_hcd:usb3
17: 66 176 1 2 11 897 3 17 IO-APIC 17-fasteoi snd_hda_intel
19: 10321 11791 4942 10065 16048 32307 16633 31262 IO-APIC 19-fasteoi ata_piix, ata_piix
23: 27 0 0 7 1 0 0 0 IO-APIC 23-fasteoi ehci_hcd:usb4
25: 0 0 0 0 0 0 0 0 PCI-MSI-edge xhci_hcd
26: 17 88916 1 470 4 0 0 9 PCI-MSI-edge eth0
27: 31025 0 1 42 2 2 0 1 PCI-MSI-edge eth1
28: 12 0 0 0 3 0 0 0 PCI-MSI-edge mei_me
29: 27 103 1 1 11 313 20 7 PCI-MSI-edge snd_hda_intel
30: 2145752 747852 565542 547596 947147 459822 339967 308636 PCI-MSI-edge nouveau
31: 10 0 0 0 0 1 0 0 PCI-MSI-edge virtex7
NMI: 74 61 58 53 68 53 40 73 Non-maskable interrupts
LOC: 1630591 1926949 1962790 2112890 550816 568671 579103 2443575 Local timer interrupts
SPU: 0 0 0 0 0 0 0 0 Spurious interrupts
PMI: 74 61 58 53 68 53 40 73 Performance monitoring interrupts
IWI: 4389 469 314 604 508 361 279 1490 IRQ work interrupts
RTR: 6 0 0 0 0 0 0 0 APIC ICR read retries
RES: 140695 148620 119993 95453 48181 41309 37573 50718 Rescheduling interrupts
CAL: 54810 89545 96134 90298 77653 83242 93564 92749 Function call interrupts
TLB: 27526 27963 26448 26049 22528 24252 25863 18098 TLB shootdowns
TRM: 0 0 0 0 0 0 0 0 Thermal event interrupts
THR: 0 0 0 0 0 0 0 0 Threshold APIC interrupts
MCE: 0 0 0 0 0 0 0 0 Machine check exceptions
MCP: 54 54 54 54 54 54 54 54 Machine check polls
HYP: 0 0 0 0 0 0 0 0 Hypervisor callback interrupts
ERR: 0
MIS: 0
doom13
Хочу ещё спросить по поводу MSI.
Добавил в ядро PCIe поддержку 4 векторов MSI. Функция pci_msi_vec_count(pdev) возвращает значение 4, т.е. всё правильно.
А вот функция pci_enable_msi_range(pdev, 1, 4) возвращает 1. Что это означает, что система может выделить только одну линию под данное устройство?
А как тогда должен работать драйвер, должен предполагать возможность обеих ситуаций?
gerber
Цитата(doom13 @ Aug 6 2015, 15:01) *
На рисунке Configuration Space для устройства PCIe есть поле Interrupt Line. Оно, как понимаю, доступно только для чтения.
Можете пояснить, что это такое и за что оно отвечает?

Насколько я помню, только для чтения поле Interrupt Pin, оно просто определяет, на какую из 4 линий INTA#-INTD# выведена линия прерывания данной платы. Исторически сложилось так, что при конструировании материнских плат линии запросов прерываний от слота к слоту идут со сдвигом, то есть в 1-м слоте PCI INTA подключается к INTA, INTB->INTB, INTC->INTC, INTD->INTD, во 2-м слоте уже INTA->INTB, INTB->INTC, INTC->INTD, INTD->INTA и т. д. Поэтому, если все слоты будут заполнены платами PCI, у которых прерывание выведено на INTA - они равномерно распределятся по всем линиям INTA-INTD. Если какая-то плата окажется с линией INTB - ничего страшного не произойдет, она разделит с другой платой одну линию прерывания.
Поэтому конструкторы периферийных плат PCI тоже не мудруствуют лукаво и в 99% случаев используют INTA.
Все 4 линии идут в мост, и уже BIOS (или ОС) решает, выделить каждой линии INTA#-INTD# по своему IRQ или всех повесить на одно IRQ. Задача BIOS - изолировать прерывания PCI-устройств от других (ISA, Legacy), которые не могут быть разделены между несколькими устройствами.

Interrupt Line регистр - это просто байтовая ячейка памяти, в которую после энумерации энумератор (BIOS или ОС) записывает выделенную для устройства и проложенную до процессора линию IRQ, чтобы драйвер мог разобраться, к чему цеплять обработчик прерывания. Никакого физического смысла, кроме ячейки памяти, поле Interrupt Line не несет.

С MSI история примерно та же, только вместо линий INTA#-INTD# используются сообщения с соответствующим номером 1-4 для совместимости, ну и с другими номерами для доп. возможностей.
doom13
Цитата(gerber @ Aug 8 2015, 18:10) *
Interrupt Line регистр - это просто байтовая ячейка памяти, в которую после энумерации энумератор (BIOS или ОС) записывает выделенную для устройства и проложенную до процессора линию IRQ, чтобы драйвер мог разобраться, к чему цеплять обработчик прерывания. Никакого физического смысла, кроме ячейки памяти, поле Interrupt Line не несет.

Каким тогда образом, если в Interrupt Line записано 11 (при выполнении функции pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &irq_num) получаю irq_num = 11), прерывание работает на линии № 16 (если MSI запрещены), либо № 31 (если MSI разрешены)?
gerber
Цитата(doom13 @ Aug 10 2015, 13:43) *
Каким тогда образом, если в Interrupt Line записано 11 (при выполнении функции pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &irq_num) получаю irq_num = 11), прерывание работает на линии № 16 (если MSI запрещены), либо № 31 (если MSI разрешены)?

Возможно, это "происки" операционной системы, то есть номера 16 и 31 являются виртуальными номерами прерываний. Так как на одном физическом номере IRQ может висеть несколько PCI-устройств - ОС должна как-то различать, какой драйвер к какому устройству подключается. Для этого ОС "придумывает" свои номера устройствам, а сама "втихаря" подвешивает свой обработчик на IRQ 11. Как только произойдёт IRQ 11 - система передает управление всем подключенным обработчикам (возможно, с приоритетом), пока кто-то из обработчиков не сообщит, что, мол, да, это моё устройство сгенерировало прерывание, и я его обработал. Так система поймет, какое именно устройство сгенерировало прерывание IRQ 11.
doom13
В драйвере есть операции работы с файлом драйвера (virtex7board_ioctl добавил недавно):
Код
static const struct file_operations virtex7board_fops= {
    .owner                = THIS_MODULE,
    .open                = virtex7board_open,
    .release            = virtex7board_release,
    .read                = virtex7board_read,
    .write                = virtex7board_write,
    .unlocked_ioctl        = virtex7board_ioctl,
};

Для работы с файлом драйвера из user space использовал fopen, fwrite, fread, fclose и всё было гуд. Теперь добавил в модуль драйвера поддержку ioctl. Тут появились какие-то ошибки. Функция fread как-то неправильно работает, она вызывает virtex7board_ioctl и virtex7board_read и передаёт неправильный размер данных для чтения в virtex7board_read (4096 байт вместо 16).
Попробовал использовать open, read, write, close - тут всё нормально.
???
doom13
Посмотрел примеры драйверов, все, оказывается, пользуются read, write???
gerber
"Нативными" методами работы с драйверами устройств являются open,read,write,close.
fopen,fread,fwrite,fclose - это уже функции библитеки libc, они удобны для работы с файлами, как с потоками данных (stream), поэтому читают из устройства (как бы файла) сразу большими блоками и буферизируют внутри себя данные. Это позволяет добиться существенного выиграша в скорости при работе с файловой системой, даже если верхний уровень читает по 16 байт, в "глубине души" чтение идёт блоками, удобными устройству и буферизируется, а когда юзер снова попросит следующие 16 байт, уже ничего читать не нужно, можно скопировать из буфера уже прочитанное...
doom13
Ясно, пользуемся open, read, write, close и всё будет работать.
Tarbal
Цитата(doom13 @ Aug 6 2015, 15:01) *
Откуда взялся номер 11 в конфигурационной области? Если его пишет система (или BIOS), то почему после переопределения номера линии прерывания он не переписывается на правильный?


Полагаю, что он задается в device tree.
doom13
Приветствую!
Возник вопрос, какой максимальный размер памяти с непрерывной адресацией можно выделить в ядре?
Посмотрел, что kmalloc, dma_alloc_coherent максимум дают 4МБ, что делать если хочу больше?
Спасибо.
doom13
Если использовать alloc_pages, то вообще выделяет только 8 страниц, далее выдаёт ошибку.
doom13
Приветствую.
В функции инициализации модуля pcievx7_init хочу добраться до struct pci_dev *pdev, вроде как должно помочь container_of, но получаются
разные значения для pdev и my_pdev. Может кто подскажет, что тут неправильно? Значения pdev->driver и &pcievx7_pci_driver одинаковы.
Код
static int pcievx7_probe(struct pci_dev *pdev , const struct pci_device_id *id)
{
    ...
    PINFO("pdev = %p\n", pdev);
    PINFO("pdev->driver = %p\n", pdev->driver); // выводит в syslog адрес pcievx7_pci_driver = DRIVER_ADDRESS
    ...
}

static struct pci_driver pcievx7_pci_driver =
{
    .name        = DRIVER_NAME,
    .probe        = pcievx7_probe,
    .remove        = pcievx7_remove,
    .id_table    = pcievx7_pci_driver_ids
};

static int __init pcievx7_init(void)
{
    struct pci_dev *my_pdev;
    ...
    pci_register_driver(&pcievx7_pci_driver);
    PINFO("&pcievx7_pci_driver = %p\n", &pcievx7_pci_driver); // выводит в syslog адрес pcievx7_pci_driver = DRIVER_ADDRESS
    my_pdev = container_of(&pcievx7_pci_driver, struct pci_dev, driver);
    PINFO("my_pdev = %p\n", my_pdev);
    ...
}
Tarbal
Я бы поле имени pdev->driver->name напечатал в консоль для обоих. Может помочь понять. Перед печатью проверьте если pdev->driver не равен нулю иначе система рухнет.
doom13
Цитата(Tarbal @ Dec 16 2015, 05:08) *
Я бы поле имени pdev->driver->name напечатал в консоль для обоих. Может помочь понять. Перед печатью проверьте если pdev->driver не равен нулю иначе система рухнет.

Если правильно Вас понял, то сделал это:
Код
static int pcievx7_probe(struct pci_dev *pdev , const struct pci_device_id *id)
{
    PINFO("%s\n", pdev->driver->name); // выводит в syslog имя драйвера "pcievx7"
}

static struct pci_driver pcievx7_pci_driver =
{
    .name        = DRIVER_NAME,
    .probe       = pcievx7_probe,
    .remove      = pcievx7_remove,
    .id_table    = pcievx7_pci_driver_ids
};

static int __init pcievx7_init(void)
{
    pci_register_driver(&pcievx7_pci_driver);
    PINFO("%s\n", (&pcievx7_pci_driver)->name); // выводит в syslog имя драйвера "pcievx7"
}

Имя драйвера в обоих случаях одинаково. Вопрос остался.

Попробовал сделать так:
Код
static int pcievx7_probe(struct pci_dev *pdev , const struct pci_device_id *id)
{
    struct pci_dev *test_pdev;

    test_pdev = container_of(pdev->driver, struct pci_dev, driver);

    PINFO("%p\n", test_pdev);
    PINFO("%p\n", pdev);
}

Получаю разные значения для test_pdev и pdev. Объясните, что тут неправильно?
Спасибо.
doom13
Надо было так:
Код
static int pcievx7_probe(struct pci_dev *pdev , const struct pci_device_id *id)
{
    struct pci_dev *test_pdev;

    test_pdev = container_of(&pdev->driver, struct pci_dev, driver);

    PINFO("%p\n", test_pdev);
    PINFO("%p\n", pdev);
}
AVR
Цитата(doom13 @ Sep 30 2015, 15:33) *
Если использовать alloc_pages, то вообще выделяет только 8 страниц, далее выдаёт ошибку.

Так это же известнейшее ограничение что нельзя сразу много страниц вподряд выделить. Для этого надо использовать SG-DMA подход (LDD3, 15.4. Direct Memory Access). Я как раз сам в процессе освоения этого.

У Вас что-то получилось по этой теме? Куча вопросов, я пока только смог сделать запись и чтение регистров своего устройства через MMIO...
dm.pogrebnoy
Цитата(AVR @ Jul 16 2016, 17:39) *
Так это же известнейшее ограничение что нельзя сразу много страниц вподряд выделить.


В последних ядрах есть CMA (Contiguous Memory Allocator), в принципе можно выделить и подряд много, сам пробовал, получалось.
AVR
Цитата(dm.pogrebnoy @ Jul 16 2016, 21:00) *
В последних ядрах есть CMA (Contiguous Memory Allocator), в принципе можно выделить и подряд много, сам пробовал, получалось.

Я может что-то не понимаю, но меня искренне удивляет что работа устройства будет отдана воле случая, выделится а может нет - зачем такое делать?

Допустим в системе 16 гигабайт ОЗУ. CMA может выделить буфер 5 гигабайт? Я знаю что так делать не хорошо, но что если будет некоторое устройство, которому именно что требуется это, и пользователь будет предупрежден что именно столько надо для работы устройства.

Я сторонник исключительно 100% надежно работающих подходов.
dm.pogrebnoy
Цитата(AVR @ Jul 16 2016, 21:21) *
Я может что-то не понимаю, но меня искренне удивляет что работа устройства будет отдана воле случая, выделится а может нет - зачем такое делать?

Допустим в системе 16 гигабайт ОЗУ. CMA может выделить буфер 5 гигабайт? Я знаю что так делать не хорошо, но что если будет некоторое устройство, которому именно что требуется это, и пользователь будет предупрежден что именно столько надо для работы устройства.

Я сторонник исключительно 100% надежно работающих подходов.


Я так понимаю, если в системе только один драйвер использует подобный алгоритм выделения памяти, то вообще никаких проблем не будет, если несколько, то надо следить за суммарным размером области. В целом да, специфично, но зато можно без SGDMA обойтись. Да и производительность чуть-чуть будет повыше.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.