-
Notifications
You must be signed in to change notification settings - Fork 4
/
pcieuni_fnc.c
157 lines (131 loc) · 4.26 KB
/
pcieuni_fnc.c
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
/**
* @file pcieuni_fnc.c
* @brief Implementation of driver specific utility functions
*/
#include "pcieuni_fnc.h"
#include <linux/sched.h>
/**
* @brief Allocates and initializes driver specific part of pci device data
*
* @param brd_num Device index in the list of probed devices
* @param pcidev Universal driver pci device structure
* @param kbuf_blk_num Number of preallocated DMA buffers
* @param kbuf_blk_size Size of preallocated DMA buffers
*
* @return Allocated module_dev structure
* @retval -ENOMEM Failed - could not allocate memory
*/
module_dev* pcieuni_create_mdev(int brd_num, pcieuni_dev* pcidev, unsigned long bufferSize) {
module_dev* mdev;
pcieuni_buffer* buffer;
ushort i;
PDEBUG(pcidev->name, "pcieuni_create_mdev(brd_num=%i)", brd_num);
#ifdef PCIEUNI_TEST_MDEV_ALLOC_FAILURE
TEST_RANDOM_EXIT(1, "PCIEUNI: Simulating failed allocation of module_dev structure!", ERR_PTR(-ENOMEM))
#endif
mdev = kzalloc(sizeof(module_dev), GFP_KERNEL);
if(!mdev) {
return ERR_PTR(-ENOMEM);
}
mdev->brd_num = brd_num;
mdev->parent_dev = pcidev;
// initalize dma buffer list
pcieuni_bufferList_init(&mdev->dmaBuffers, pcidev);
// allocate DMA buffers
for(i = 0; i < 2; i++) {
buffer = pcieuni_buffer_create(pcidev, bufferSize);
if(IS_ERR(buffer)) break;
pcieuni_bufferList_append(&mdev->dmaBuffers, buffer);
}
if(IS_ERR(buffer)) {
printk(KERN_ERR "pcieuni(%s): failed to allocate DMA buffers!\n", pcidev->name);
pcieuni_bufferList_clear(&mdev->dmaBuffers);
}
init_waitqueue_head(&mdev->waitDMA);
sema_init(&mdev->dma_sem, 1);
mdev->waitFlag = 1;
mdev->dma_buffer = 0;
return mdev;
}
/**
* @brief Releses driver specific part of pci device data
* @note This function may block (in case some DMA buffers are in use)
*
* @param mdev module_dev sructure to be released
* @return void
*/
void pcieuni_release_mdev(module_dev* mdev) {
if(!IS_ERR_OR_NULL(mdev)) {
PDEBUG(mdev->parent_dev->name, "pcieuni_release_mdev()");
// clear the buffers gracefully
pcieuni_bufferList_clear(&mdev->dmaBuffers);
// clear the module_dev structure
kfree(mdev);
}
}
/**
* @brief Returns driver specific part of pci device data
*
* @param dev Universal driver pci device structure
* @return module_dev structure
*/
module_dev* pcieuni_get_mdev(struct pcieuni_dev* dev) {
struct module_dev* mdev = (struct module_dev*)(dev->dev_str);
return mdev;
}
/**
* @brief Reserves DMA read process on target device
*
* @note This function will block until device is ready to acceept DMA request.
*
* @param mdev PCI device
* @param buffer Target DMA buffer
*
* @retval 0 Success
* @retval -EINTR Operation was interrupted
* @retval -EBUSY Operation timed out
*/
int pcieuni_dma_reserve(module_dev* mdev, pcieuni_buffer* buffer) {
long waitVal = 0;
long timeout = HZ / 1;
PDEBUG(mdev->parent_dev->name, "pcieuni_dma_reserve()");
// protect against concurrent reservation of waitFlag
down(&mdev->dma_sem);
while(!mdev->waitFlag) {
// wait for DMA to be available
up(&mdev->dma_sem);
PDEBUG(mdev->parent_dev->name, "pcieuni_dma_reserve(): Waiting until dma available...\n");
waitVal = wait_event_timeout(mdev->waitDMA, mdev->waitFlag, timeout);
if(0 == waitVal) {
printk(KERN_ERR "pcieuni(%s): error waiting for DMA to become available: Timeout!\n", mdev->parent_dev->name);
return -EBUSY;
}
else if(0 > waitVal) {
PDEBUG(mdev->parent_dev->name, "pcieuni_dma_reserve(): Interrupted!\n");
return -EINTR;
}
#ifdef PCIEUNI_TEST_DEVICE_DMA_BLOCKED
TEST_RANDOM_EXIT(100, "PCIEUNI: Simulating blocked DMA !", -EBUSY)
#endif
// protect against concurrent reservation of waitFlag
down(&mdev->dma_sem);
}
// Mark DMA read operation reserved and set the target buffer
mdev->waitFlag = 0;
mdev->dma_buffer = buffer;
up(&mdev->dma_sem);
return 0;
}
/**
* @brief Releases DMA read process on target device
*
* @note This function is called from interrupt handler so it must not block.
*
* @param mdev Driver device structure
*/
void pcieuni_dma_release(module_dev* mdev) {
PDEBUG(mdev->parent_dev->name, "pcieuni_dma_release()");
mdev->waitFlag = 1;
mdev->dma_buffer = 0;
wake_up(&(mdev->waitDMA));
}