Ошибка ядра при выполнении функции чтения аппаратных регистров

Я ссылаюсь на этот ответ о сбое, помогающий проанализировать этот фрагмент кода, который вызвал проблемы. Контекст для всех, я работаю над символьным драйвером, который будет действовать как переход из пользовательского пространства непосредственно к оборудованию для драйвера ahci. Для этого я соответствующим образом модифицирую драйвер ahci.

Я начинаю с малого. Я хочу заглянуть в регистры портов для порта 0 HBA HBA AHCI на моей виртуальной машине. Код ioctl моего персонажа:

switch (cmd) {
    case AHCIP_GPORT_REG:
        pPciDev = pci_get_device(0x8086, 0x2829, NULL);

        if (pPciDev) {
            /* This will set ret to the value that it needs to be.  This
             * is true of __put_user() too */
            if ((ret = __get_user(off, (u32*)obj))) {
                printk(KERN_INFO "unable to read from user space\n");
                goto ioctl_quick_out;
            }

            reg = get_port_reg(&pPciDev->dev, off);
            if ((ret = __put_user(reg, (u32*)obj)))
            {
                printk(KERN_INFO "Unable to write to user space\n");
            }

            pci_dev_put(pPciDev);
        }

        // This break wasn't in the code when it crashed
        break;

    default:
        // POSIX compliance with this one (REF of LDD3)
        ret = -ENOTTY;
}

Код из моей модифицированной версии ahci.c, который вызывает этот символьный драйвер:

u32 get_port_reg(struct device *dev, u32 off)
{
    struct Scsi_Host *shost = class_to_shost(dev);
    struct ata_port *ap = ata_shost_to_port(shost);
    void __iomem *port_mmio = ahci_port_base(ap);

    return ioread32(port_mmio + off);
}
EXPORT_SYMBOL(get_port_reg);

Ядро, которое это вызвало, произошло здесь:

PID: 3357   TASK: ffff88011c9b7500  CPU: 0   COMMAND: "peek"
 #0 [ffff8800abfc79f0] machine_kexec at ffffffff8103b5bb
 #1 [ffff8800abfc7a50] crash_kexec at ffffffff810c9852
 #2 [ffff8800abfc7b20] oops_end at ffffffff8152e0f0
 #3 [ffff8800abfc7b50] no_context at ffffffff8104c80b
 #4 [ffff8800abfc7ba0] __bad_area_nosemaphore at ffffffff8104ca95
 #5 [ffff8800abfc7bf0] bad_area at ffffffff8104cbbe
 #6 [ffff8800abfc7c20] __do_page_fault at ffffffff8104d36f
 #7 [ffff8800abfc7d40] do_page_fault at ffffffff8153003e
 #8 [ffff8800abfc7d70] page_fault at ffffffff8152d3f5
    [exception RIP: get_port_reg+18]
    RIP: ffffffffa03c4cd2  RSP: ffff8800abfc7e28  RFLAGS: 00010246
    RAX: 0000000000020101  RBX: 00007fff17273960  RCX: ffffffff812b0710
    RDX: ffff88011ddd5000  RSI: 0000000000000000  RDI: ffff88011ddd5090
    RBP: ffff8800abfc7e28   R8: 0000000000000000   R9: 0000000000000000
    R10: 00000000000007d5  R11: 0000000000000006  R12: ffff88011ddd5000
    R13: 0000000000000000  R14: 0000000000000000  R15: 0000000000000000
    ORIG_RAX: ffffffffffffffff  CS: 0010  SS: 0018

Как видите, указатель инструкции был get_port_reg+18. Так как эта функция совсем небольшая, вот полная разборка

crash> dis get_port_reg
0xffffffffa03c4cc0 <get_port_reg>:      push   %rbp
0xffffffffa03c4cc1 <get_port_reg+1>:    mov    %rsp,%rbp
0xffffffffa03c4cc4 <get_port_reg+4>:    nopl   0x0(%rax,%rax,1)
0xffffffffa03c4cc9 <get_port_reg+9>:    mov    0x240(%rdi),%rax
0xffffffffa03c4cd0 <get_port_reg+16>:   mov    %esi,%esi
0xffffffffa03c4cd2 <get_port_reg+18>:   mov    0x2838(%rax),%rdx
0xffffffffa03c4cd9 <get_port_reg+25>:   mov    0x28(%rax),%eax
0xffffffffa03c4cdc <get_port_reg+28>:   mov    0x10(%rdx),%rdx
0xffffffffa03c4ce0 <get_port_reg+32>:   shl    $0x7,%eax
0xffffffffa03c4ce3 <get_port_reg+35>:   mov    %eax,%eax
0xffffffffa03c4ce5 <get_port_reg+37>:   add    0x28(%rdx),%rax
0xffffffffa03c4ce9 <get_port_reg+41>:   lea    0x100(%rax,%rsi,1),%rdi
0xffffffffa03c4cf1 <get_port_reg+49>:   callq  0xffffffff8129dde0 <ioread32>
0xffffffffa03c4cf6 <get_port_reg+54>:   leaveq 
0xffffffffa03c4cf7 <get_port_reg+55>:   retq   
0xffffffffa03c4cf8 <get_port_reg+56>:   nopl   0x0(%rax,%rax,1)

Как вы уже догадались, я в некотором роде новичок в сборке. Какая строка кода будет get_port_reg+18? Я озадачен, потому что я вызываю функции в каждой строке этой функции, но единственный вызов, который я вижу, - это ioread32().

Для справки, я смоделировал свою функцию get_port_reg после ahci_show_port_cmd() в том же файл. Я не мог придумать никаких других средств получения структуры struct pci_dev, необходимой для ее работы. Я плохо использую get_pci_device() и pci_dev_put()? Разве это не проблема?

Спасибо за любую помощь
Энди


person Andrew Falanga    schedule 11.05.2015    source источник
comment
pci_get_device(), вероятно, возвращает устройство pci, в то время как вам нужно соответствующее хост-устройство SCSI - они имеют разные классы.   -  person myaut    schedule 11.05.2015
comment
Посмотрите на ata_pci_remove_one: lxr.free-electrons.com/ source / drivers / ata / libata-core.c # L6314 показывает, как получить ata_host с устройства PCI, и получение ata_port с него должно быть простым.   -  person myaut    schedule 11.05.2015
comment
Похоже, проблема здесь - ata_shost_to_port. Я могу сказать вам только с технической точки зрения, без понимания кода: функция class_to_shost в основном является container_of, поэтому она предполагает, что struct device (dev) встроена в struct Scsi_host, поэтому она соответствующим образом приводит dev к Scsi_host. Затем ваш код пытается разыменовать shost, чтобы получить ata_port (* (struct ata_port **) & host- ›hostdata [0]). И бум ... похоже, что тут произошла page_fault. ТАК это означает, что, вероятно, вместо struct Scsi_host есть мусор ...   -  person Alex Hoppus    schedule 12.05.2015


Ответы (1)


Я собираюсь опубликовать свой ответ. Два комментатора моего вопроса направили меня на правильный путь, чтобы исправить это. Как я уже упоминал, мой подход заключался в том, чтобы сделать что-то, что я видел в другом месте в драйвере ahci (ahci.c). По сути, предположение было простым, эта функция в ahci.c требовался struct device* и от него можно было получить ata_port информацию, которая требовалась. Я видел в ahci.c, что автор иногда делал struct device* = &pdev->dev;. Другими словами, я решил, что участник dev из struct pci_dev получает то, что мне нужно. Я, очевидно, не знал о «типах классов» или о чем-то подобном (см. Первый комментарий @myaut). @alexhoppus по сути делает такой же / аналогичный вывод на основе кода и разборки, которые я опубликовал.

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

/* ioctl code in character driver */
switch (cmd) {
    case AHCIP_GPORT_REG:
        pPciDev = pci_get_device(0x8086, 0x2829, NULL);

        if (pPciDev) {
            struct ata_host *pHost = NULL;
            struct ata_port *pPort = NULL;
            printk(KERN_INFO "found the PCI device\n");
            /* Get the devices driver data */
            pHost = pci_get_drvdata(pPciDev);
            if (!pHost) {
                ret = -EFAULT;
                goto ioctl_valid_pci_dev_out;
            }

            /* for this test, we'll use just port 0 */
            pPort = pHost->ports[0];
            if (!pPort) {
                ret = -EFAULT;
                goto ioctl_valid_pci_dev_out;
            }

            /* This will set ret to the value that it needs to be.  This
             * is true of __put_user() too */
            if ((ret = __get_user(off, (u32*)obj))) {
                printk(KERN_INFO "unable to read from user space\n");
                goto ioctl_valid_pci_dev_out;
            }

            reg = get_port_reg(pPort, off);
            if ((ret = __put_user(reg, (u32*)obj)))
            {
                printk(KERN_INFO "Unable to write to user space\n");
            }
        }

        break;

    default:
        // POSIX compliance with this one (REF of LDD3)
        ret = -ENOTTY;
}

Таким же образом был изменен и драйвер ahci.

u32 get_port_reg(struct ata_port* pPort, u32 off)
{
    void __iomem *port_mmio = ahci_port_base(pPort);

    return ioread32(port_mmio + off);
}
EXPORT_SYMBOL(get_port_reg);

Хотя это устранило проблему для меня, я был бы очень признателен, если бы кто-нибудь объяснил мне, что находится в (struct pci_dev)device.dev.p->driver_data. I can use, and have, the Linux cross referencing tools to see the data types. What is supposed to be stored instruct device_private`? Это структура, которую я сейчас использую для получения данные мне нужны. Я был бы очень признателен, если бы кто-то прокомментировал этот ответ, чтобы объяснить его.

Спасибо @myaut и @alexhoppus

person Andrew Falanga    schedule 12.05.2015