Ядрото опа, когато изпълнява функция за четене на хардуерни регистри

Позовавам се на този отговор за срив помощ при анализирането на тази част от кода, която е причинила проблеми. Контекстът за всички е, че работя с символен драйвер, който ще действа като пропуск от потребителското пространство директно към хардуера, за ahci драйвера. Модифицирам ahci драйвера съответно за тази цел.

Започвам с малко. Искам да надникна в порт регистрите за HBA порт 0 на AHCI HBA на моята виртуална машина. Моят 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);

Ядрото oops, което това причини, се случи тук:

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 (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