/alps/pcitool

To get this branch, use:
bzr branch http://suren.me/webbzr/alps/pcitool
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/**
 *
 * @file int.c
 * @author Guillermo Marcus
 * @date 2009-04-05
 * @brief Contains the interrupt handler.
 *
 */

#include <linux/version.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <stdbool.h>

#include "base.h"

/**
 *
 * Acknowledges the receival of an interrupt to the card.
 *
 * @returns true if the card was acknowledget
 * @returns false if the interrupt was not for one of our cards
 *
 * @see check_acknowlegde_channel
 *
 */
static bool pcidriver_irq_acknowledge(pcidriver_privdata_t *privdata)
{
    int channel = 0;

    atomic_inc(&(privdata->irq_outstanding[channel]));
    wake_up_interruptible(&(privdata->irq_queues[channel]));

    return true;
}

/**
 *
 * Handles IRQs. At the moment, this acknowledges the card that this IRQ
 * was received and then increases the driver's IRQ counter.
 *
 * @see pcidriver_irq_acknowledge
 *
 */
static irqreturn_t pcidriver_irq_handler(int irq, void *dev_id)
{
    pcidriver_privdata_t *privdata = (pcidriver_privdata_t *)dev_id;

    if (!pcidriver_irq_acknowledge(privdata))
        return IRQ_NONE;

    privdata->irq_count++;
    return IRQ_HANDLED;
}


/**
 *
 * If IRQ-handling is enabled, this function will be called from pcidriver_probe
 * to initialize the IRQ handling (maps the BARs)
 *
 */
int pcidriver_probe_irq(pcidriver_privdata_t *privdata)
{
    unsigned char int_pin, int_line;
    unsigned long bar_addr, bar_len, bar_flags;
    int i;
    int err;

    for (i = 0; i < 6; i++)
        privdata->bars_kmapped[i] = NULL;

    for (i = 0; i < 6; i++) {
        bar_addr = pci_resource_start(privdata->pdev, i);
        bar_len = pci_resource_len(privdata->pdev, i);
        bar_flags = pci_resource_flags(privdata->pdev, i);

        /* check if it is a valid BAR, skip if not */
        if ((bar_addr == 0) || (bar_len == 0))
            continue;

        /* Skip IO regions (map only mem regions) */
        if (bar_flags & IORESOURCE_IO)
            continue;

        /* Check if the region is available */
        if ((err = pci_request_region(privdata->pdev, i, NULL)) != 0) {
            mod_info( "Failed to request BAR memory region.\n" );
            return err;
        }

        /* Map it into kernel space. */
        /* For x86 this is just a dereference of the pointer, but that is
         * not portable. So we need to do the portable way. Thanks Joern!
         */

        /* respect the cacheable-bility of the region */
        if (bar_flags & IORESOURCE_PREFETCH)
            privdata->bars_kmapped[i] = ioremap(bar_addr, bar_len);
        else
            privdata->bars_kmapped[i] = ioremap_nocache(bar_addr, bar_len);

        /* check for error */
        if (privdata->bars_kmapped[i] == NULL) {
            mod_info( "Failed to remap BAR%d into kernel space.\n", i );
            return -EIO;
        }
    }

    /* Initialize the interrupt handler for this device */
    /* Initialize the wait queues */
    for (i = 0; i < PCIDRIVER_INT_MAXSOURCES; i++) {
        init_waitqueue_head(&(privdata->irq_queues[i]));
        atomic_set(&(privdata->irq_outstanding[i]), 0);
    }

    /* Initialize the irq config */
    if ((err = pci_read_config_byte(privdata->pdev, PCI_INTERRUPT_PIN, &int_pin)) != 0) {
        /* continue without interrupts */
        int_pin = 0;
        mod_info("Error getting the interrupt pin. Disabling interrupts for this device\n");
    }

    /* Disable interrupts and activate them if everything can be set up properly */
    privdata->irq_enabled = 0;

    if (int_pin == 0)
        return 0;

    if ((err = pci_read_config_byte(privdata->pdev, PCI_INTERRUPT_LINE, &int_line)) != 0) {
        mod_info("Error getting the interrupt line. Disabling interrupts for this device\n");
        return 0;
    }

    /* Enable interrupts using MSI mode */
    if (!pci_enable_msi(privdata->pdev))
        privdata->msi_mode = 1;

    /* register interrupt handler */
    if ((err = request_irq(privdata->pdev->irq, pcidriver_irq_handler, IRQF_SHARED, MODNAME, privdata)) != 0) {
        mod_info("Error registering the interrupt handler. Disabling interrupts for this device\n");
        return 0;
    }

    privdata->irq_enabled = 1;
    mod_info("Registered Interrupt Handler at pin %i, line %i, IRQ %i\n", int_pin, int_line, privdata->pdev->irq );

    return 0;
}

/**
 *
 * Frees/cleans up the data structures, called from pcidriver_remove()
 *
 */
void pcidriver_remove_irq(pcidriver_privdata_t *privdata)
{
    /* Release the IRQ handler */
    if (privdata->irq_enabled != 0)
        free_irq(privdata->pdev->irq, privdata);

    if (privdata->msi_mode) {
        pci_disable_msi(privdata->pdev);
        privdata->msi_mode = 0;
    }

    pcidriver_irq_unmap_bars(privdata);
}

/**
 *
 * Unmaps the BARs and releases them
 *
 */
void pcidriver_irq_unmap_bars(pcidriver_privdata_t *privdata)
{
    int i;

    for (i = 0; i < 6; i++) {
        if (privdata->bars_kmapped[i] == NULL)
            continue;

        iounmap((void*)privdata->bars_kmapped[i]);
        pci_release_region(privdata->pdev, i);
    }
}