-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathchardev.c
414 lines (322 loc) · 11.1 KB
/
chardev.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
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc.
// SPDX-License-Identifier: GPL-2.0-only
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include "chardev.h"
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#include "chardev_private.h"
#include "device.h"
#include "enumerate.h"
#include "ioctl.h"
#include "pcie.h"
#include "memory.h"
static dev_t tt_device_id;
static struct class *tt_dev_class;
static unsigned int tt_max_devices;
static long tt_cdev_ioctl(struct file *, unsigned int, unsigned long);
static int tt_cdev_mmap(struct file *, struct vm_area_struct *);
static int tt_cdev_open(struct inode *, struct file *);
static int tt_cdev_release(struct inode *, struct file *);
static struct file_operations chardev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = tt_cdev_ioctl,
.mmap = tt_cdev_mmap,
.open = tt_cdev_open,
.release = tt_cdev_release,
};
int init_char_driver(unsigned int max_devices)
{
int res;
tt_max_devices = max_devices;
// Allocate a device major/minor (one minor) for this driver.
res = alloc_chrdev_region(&tt_device_id, 0, max_devices, TENSTORRENT);
if (res < 0)
goto alloc_chrdev_region_failed;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
tt_dev_class = class_create(TENSTORRENT);
#else
tt_dev_class = class_create(THIS_MODULE, TENSTORRENT);
#endif
if (IS_ERR(tt_dev_class)) {
tt_dev_class = NULL;
goto class_create_failed;
}
return 0;
class_create_failed:
unregister_chrdev_region(tt_device_id, max_devices);
alloc_chrdev_region_failed:
return res;
}
void cleanup_char_driver(void)
{
class_destroy(tt_dev_class);
tt_dev_class = NULL;
unregister_chrdev_region(tt_device_id, tt_max_devices);
}
static dev_t devt_for_device(struct tenstorrent_device *tt_dev)
{
return MKDEV(MAJOR(tt_device_id), MINOR(tt_device_id) + tt_dev->ordinal);
}
int tenstorrent_register_device(struct tenstorrent_device *tt_dev)
{
dev_t devt = devt_for_device(tt_dev);
device_initialize(&tt_dev->dev);
tt_dev->dev.devt = devt;
tt_dev->dev.class = tt_dev_class;
tt_dev->dev.parent = &tt_dev->pdev->dev;
tt_dev->dev.groups = NULL;
tt_dev->dev.release = NULL;
tt_dev->dev.id = tt_dev->ordinal;
dev_set_name(&tt_dev->dev, TENSTORRENT "/%d", tt_dev->ordinal);
cdev_init(&tt_dev->chardev, &chardev_fops);
return cdev_device_add(&tt_dev->chardev, &tt_dev->dev);
}
void tenstorrent_unregister_device(struct tenstorrent_device *tt_dev)
{
cdev_device_del(&tt_dev->chardev, &tt_dev->dev);
}
static long ioctl_get_device_info(struct chardev_private *priv,
struct tenstorrent_get_device_info __user *arg)
{
const struct pci_dev *pdev = priv->device->pdev;
u32 bytes_to_copy;
struct tenstorrent_get_device_info_in in;
struct tenstorrent_get_device_info_out out;
memset(&in, 0, sizeof(in));
memset(&out, 0, sizeof(out));
if (copy_from_user(&in, &arg->in, sizeof(in)) != 0)
return -EFAULT;
out.output_size_bytes = sizeof(out);
out.vendor_id = pdev->vendor;
out.device_id = pdev->device;
out.subsystem_vendor_id = pdev->subsystem_vendor;
out.subsystem_id = pdev->subsystem_device;
out.bus_dev_fn = PCI_DEVID(pdev->bus->number, pdev->devfn);
out.max_dma_buf_size_log2 = MAX_DMA_BUF_SIZE_LOG2;
out.pci_domain = pci_domain_nr(pdev->bus);
if (clear_user(&arg->out, in.output_size_bytes) != 0)
return -EFAULT;
bytes_to_copy = min(in.output_size_bytes, (u32)sizeof(out));
if (copy_to_user(&arg->out, &out, bytes_to_copy) != 0)
return -EFAULT;
return 0;
}
static long ioctl_get_driver_info(struct chardev_private *priv,
struct tenstorrent_get_driver_info __user *arg)
{
u32 bytes_to_copy;
struct tenstorrent_get_driver_info_in in;
struct tenstorrent_get_driver_info_out out;
memset(&in, 0, sizeof(in));
memset(&out, 0, sizeof(out));
if (copy_from_user(&in, &arg->in, sizeof(in)) != 0)
return -EFAULT;
out.output_size_bytes = sizeof(out);
out.driver_version = TENSTORRENT_DRIVER_VERSION;
if (clear_user(&arg->out, in.output_size_bytes) != 0)
return -EFAULT;
bytes_to_copy = min(in.output_size_bytes, (u32)sizeof(out));
if (copy_to_user(&arg->out, &out, bytes_to_copy) != 0)
return -EFAULT;
return 0;
}
static long ioctl_reset_device(struct chardev_private *priv,
struct tenstorrent_reset_device __user *arg)
{
struct pci_dev *pdev = priv->device->pdev;
bool ok;
u32 bytes_to_copy;
struct tenstorrent_reset_device_in in;
struct tenstorrent_reset_device_out out;
memset(&in, 0, sizeof(in));
memset(&out, 0, sizeof(out));
if (copy_from_user(&in, &arg->in, sizeof(in)) != 0)
return -EFAULT;
if (in.flags == TENSTORRENT_RESET_DEVICE_RESTORE_STATE) {
if (safe_pci_restore_state(pdev))
ok = priv->device->dev_class->init_hardware(priv->device);
else
ok = false;
} else if (in.flags == TENSTORRENT_RESET_DEVICE_RESET_PCIE_LINK) {
ok = pcie_hot_reset_and_restore_state(pdev);
} else if (in.flags == TENSTORRENT_RESET_DEVICE_CONFIG_WRITE) {
ok = pcie_timer_interrupt(pdev);
} else {
return -EINVAL;
}
out.output_size_bytes = sizeof(out);
out.result = !ok;
if (clear_user(&arg->out, in.output_size_bytes) != 0)
return -EFAULT;
bytes_to_copy = min(in.output_size_bytes, (u32)sizeof(out));
if (copy_to_user(&arg->out, &out, bytes_to_copy) != 0)
return -EFAULT;
return 0;
}
static long ioctl_lock_ctl(struct chardev_private *priv,
struct tenstorrent_lock_ctl __user *arg) {
u32 bytes_to_copy;
struct tenstorrent_lock_ctl_in in;
struct tenstorrent_lock_ctl_out out;
memset(&in, 0, sizeof(in));
memset(&out, 0, sizeof(out));
if (copy_from_user(&in, &arg->in, sizeof(in)) != 0)
return -EFAULT;
if (in.index >= TENSTORRENT_RESOURCE_LOCK_COUNT)
return -EINVAL;
switch (in.flags) {
case TENSTORRENT_LOCK_CTL_ACQUIRE:
// If the global lock was unset then the local one must also be unset.
// In a race this won't desync because the global lock is set first in the lock
// and unset last in the unlock.
if (!test_and_set_bit(in.index, priv->device->resource_lock)) {
set_bit(in.index, priv->resource_lock);
// 1 means that the lock was aquired.
out.value = 1;
} else {
// 0 means that the lock failed to be aquired.
out.value = 0;
}
break;
case TENSTORRENT_LOCK_CTL_RELEASE:
// First check the local lock, it is set last when locking so must be unset first when unlocking.
if (test_and_clear_bit(in.index, priv->resource_lock)) {
clear_bit(in.index, priv->device->resource_lock);
// 1 means that the lock was released.
out.value = 1;
} else {
// 0 means that the lock failed to be released.
out.value = 0;
}
break;
case TENSTORRENT_LOCK_CTL_TEST:
// The local view goes in the first bit and the global goes in the second.
// This should ensure that you can still convert to a bool in order to check if the lock
// has been set at all, but also allows means that the caller doesn't always needs to do their own bookkeeping.
out.value = (test_bit(in.index, priv->device->resource_lock) << 1) |
test_bit(in.index, priv->resource_lock);
break;
default:
return -EINVAL;
}
if (clear_user(&arg->out, in.output_size_bytes) != 0)
return -EFAULT;
bytes_to_copy = min(in.output_size_bytes, (u32)sizeof(out));
if (copy_to_user(&arg->out, &out, bytes_to_copy) != 0)
return -EFAULT;
return 0;
}
static long tt_cdev_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
long ret = -EINVAL;
struct chardev_private *priv = f->private_data;
switch (cmd) {
case TENSTORRENT_IOCTL_GET_DEVICE_INFO:
ret = ioctl_get_device_info(priv, (struct tenstorrent_get_device_info __user *)arg);
break;
case TENSTORRENT_IOCTL_GET_HARVESTING:
break;
case TENSTORRENT_IOCTL_QUERY_MAPPINGS:
ret = ioctl_query_mappings(priv, (struct tenstorrent_query_mappings __user *)arg);
break;
case TENSTORRENT_IOCTL_ALLOCATE_DMA_BUF:
ret = ioctl_allocate_dma_buf(priv, (struct tenstorrent_allocate_dma_buf __user *)arg);
break;
case TENSTORRENT_IOCTL_FREE_DMA_BUF:
ret = ioctl_free_dma_buf(priv, (struct tenstorrent_free_dma_buf __user *)arg);
break;
case TENSTORRENT_IOCTL_GET_DRIVER_INFO:
ret = ioctl_get_driver_info(priv, (struct tenstorrent_get_driver_info __user *)arg);
break;
case TENSTORRENT_IOCTL_RESET_DEVICE:
ret = ioctl_reset_device(priv, (struct tenstorrent_reset_device __user *)arg);
break;
case TENSTORRENT_IOCTL_PIN_PAGES:
ret = ioctl_pin_pages(priv, (struct tenstorrent_pin_pages __user *)arg);
break;
case TENSTORRENT_IOCTL_LOCK_CTL:
ret = ioctl_lock_ctl(priv, (struct tenstorrent_lock_ctl __user *)arg);
break;
case TENSTORRENT_IOCTL_MAP_PEER_BAR:
ret = ioctl_map_peer_bar(priv, (struct tenstorrent_map_peer_bar __user *)arg);
break;
case TENSTORRENT_IOCTL_UNPIN_PAGES:
ret = ioctl_unpin_pages(priv, (struct tenstorrent_unpin_pages __user *)arg);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int tt_cdev_mmap(struct file *file, struct vm_area_struct *vma)
{
struct chardev_private *priv = file->private_data;
return tenstorrent_mmap(priv, vma);
}
static struct tenstorrent_device *inode_to_tt_dev(struct inode *inode)
{
return container_of(inode->i_cdev, struct tenstorrent_device, chardev);
}
static void increment_cdev_open_count(struct tenstorrent_device *tt_dev) {
mutex_lock(&tt_dev->chardev_mutex);
if (!tt_dev->chardev_open_count && tt_dev->dev_class->first_open_cb)
tt_dev->dev_class->first_open_cb(tt_dev);
tt_dev->chardev_open_count++;
mutex_unlock(&tt_dev->chardev_mutex);
}
static void decrement_cdev_open_count(struct tenstorrent_device *tt_dev) {
mutex_lock(&tt_dev->chardev_mutex);
tt_dev->chardev_open_count--;
if (!tt_dev->chardev_open_count && tt_dev->dev_class->last_release_cb)
tt_dev->dev_class->last_release_cb(tt_dev);
mutex_unlock(&tt_dev->chardev_mutex);
}
static int tt_cdev_open(struct inode *inode, struct file *file)
{
struct tenstorrent_device *tt_dev = inode_to_tt_dev(inode);
struct chardev_private *private_data;
private_data = kzalloc(sizeof(*private_data), GFP_KERNEL);
if (private_data == NULL)
return -ENOMEM;
mutex_init(&private_data->mutex);
hash_init(private_data->dmabufs);
INIT_LIST_HEAD(&private_data->pinnings);
INIT_LIST_HEAD(&private_data->peer_mappings);
kref_get(&tt_dev->kref);
private_data->device = tt_dev;
file->private_data = private_data;
increment_cdev_open_count(tt_dev);
return 0;
}
static int tt_cdev_release(struct inode *inode, struct file *file)
{
struct chardev_private *priv = file->private_data;
struct tenstorrent_device *tt_dev = priv->device;
unsigned int bitpos;
decrement_cdev_open_count(tt_dev);
tenstorrent_memory_cleanup(priv);
// Release all locally held resources.
for (bitpos = 0; bitpos < TENSTORRENT_RESOURCE_LOCK_COUNT; ++bitpos) {
// Same as in the ioctl handler, first clear the local data because it is set last during the lock.
if (test_and_clear_bit(bitpos, priv->resource_lock))
clear_bit(bitpos, priv->device->resource_lock);
}
tenstorrent_device_put(tt_dev);
kfree(file->private_data);
file->private_data = NULL;
return 0;
}
struct chardev_private *get_tenstorrent_priv(struct file *f)
{
if (f->f_op != &chardev_fops)
return NULL;
return f->private_data;
}