forked from kolasa/fglrx-13.250
-
Notifications
You must be signed in to change notification settings - Fork 0
/
kcl_iommu.c
253 lines (223 loc) · 8.02 KB
/
kcl_iommu.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
/****************************************************************************
* *
* Copyright 2010-2011 ATI Technologies Inc., Markham, Ontario, CANADA. *
* All Rights Reserved. *
* *
* Your use and or redistribution of this software in source and \ or *
* binary form, with or without modification, is subject to: (i) your *
* ongoing acceptance of and compliance with the terms and conditions of *
* the ATI Technologies Inc. software End User License Agreement; and (ii) *
* your inclusion of this notice in any version of this software that you *
* use or redistribute. A copy of the ATI Technologies Inc. software End *
* User License Agreement is included with this software and is also *
* available by contacting ATI Technologies Inc. at http://www.ati.com *
* *
****************************************************************************/
/** \brief Implementation of KCL IOMMU supporting interfaces
*
* CONVENTIONS
*
* Public symbols:
* - prefixed with KCL_IOMMU
* - are not static
* - declared in the corresponding header
*
* Private symbols:
* - prefixed with kcl
* - are static
* - not declared in the corresponding header
*
*/
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33)
#include <generated/autoconf.h>
#else
#include <linux/autoconf.h>
#endif
#include <linux/pci.h>
#include <linux/acpi.h>
#include "kcl_config.h"
#include "kcl_osconfig.h"
#include "kcl_type.h"
#include "kcl_iommu.h"
#include "kcl_acpi.h"
#include "kcl_debug.h"
#include "firegl_public.h"
#if defined(CONFIG_AMD_IOMMU_V2) || defined(CONFIG_AMD_IOMMU_V2_MODULE)
#include <linux/amd-iommu.h>
#include <linux/kthread.h>
#define IOMMUV2_SUPPORT 1
#endif
//temporarily lay it global in iommu field. keep iommu hack being transparent to driver
u8 *iommu_base;
/** \brief Registers a peripheral device to the IOMMU driver.
* \param pcidev [in] PCI device handle
* \param pPasids [out] num of maximum PASIDs supported.
* \return 0 success, failed other wise.
*/
int ATI_API_CALL KCL_IOMMU_InitDevice( KCL_PCI_DevHandle pcidev,KCL_IOMMU_info_t* pInfo)
{
#ifdef IOMMUV2_SUPPORT
struct amd_iommu_device_info info;
struct pci_dev* pdev = (struct pci_dev*)pcidev;
memset(&info, 0 , sizeof(info));
if(amd_iommu_device_info(pdev, &info))
{
return -1;
}
pInfo->max_pasids = info.max_pasids;
pInfo->flags.raw = info.flags;
if(pInfo->flags.f.ats_sup && pInfo->flags.f.pri_sup && pInfo->flags.f.pasid_sup && pInfo->max_pasids)
{
int i ;
for(i = 0; i < sizeof(pInfo->erratum_mask)* 8;++i)
{
if ( (pInfo->erratum_mask >> i) & 1 )
{
amd_iommu_enable_device_erratum(pdev, i);
}
}
return amd_iommu_init_device(pdev,info.max_pasids);
}
#endif
return -1;
}
/** \brief Unregisters a peripheral device from the IOMMU
* \param pcidev [in] PCI device handle
*
*/
void ATI_API_CALL KCL_IOMMU_FreeDevice( KCL_PCI_DevHandle pcidev)
{
#ifdef IOMMUV2_SUPPORT
struct pci_dev* pdev = (struct pci_dev*)pcidev;
amd_iommu_free_device(pdev);
#endif
}
#ifdef IOMMUV2_SUPPORT
//Call back function from iommu driver when OS could not successfully
//resolve an IO page-fault signaled by our GPU via PRI request
static int kcl_iommu_invalid_pri_request( struct pci_dev* pdev,
int pasid,
unsigned long fault_addr,
unsigned int perm)
{
KCL_IOMMU_req_perm_t kcl_perm;
kcl_perm.all = perm;
return libip_iommu_invalid_pri_request( (KCL_PCI_DevHandle)pdev, pasid, fault_addr, kcl_perm);
}
//Register a call-back for invalidating a pasid context. This call-back is
//invoked when the IOMMUv2 driver needs to invalidate a PASID context, for example
//because the task that is bound to that context is about to exit but the unbind_pasid is not been called yet
static void kcl_iommu_invalidate_ctx( struct pci_dev* pdev,int pasid)
{
libip_iommu_invalidate_pasid_ctx((KCL_PCI_DevHandle)pdev, pasid);
}
#endif
/** \brief Binds a peripheral's execution context identified by a PASID
* to a process' virtual address space.
* \param pcidev [in] PCI device handle
* \param pid [in] process ID of thread group leader.(First thread of the process)
* \param pasid [in] specified PASID.
* \return 0 success, failed other wise.
*/
int ATI_API_CALL KCL_IOMMU_BindPasid( KCL_PCI_DevHandle pcidev,int pid,int pasid)
{
int ret = 0;
#ifdef IOMMUV2_SUPPORT
struct pci_dev* pdev = (struct pci_dev*)pcidev;
struct task_struct *group_leader = current->group_leader;
if(pid == group_leader->pid)
{
ret = amd_iommu_bind_pasid( pdev,
pasid,
group_leader ); //task of thread group leader
}
if(ret)
{
KCL_DEBUG_ERROR("pid:0x%x, group_leader id:0x%x, pasid:0x%x, ret:0x%x.\n", pid,group_leader->pid,pasid,ret);
return ret;
}
//register call back on invalid PPR.
ret = amd_iommu_set_invalid_ppr_cb( pdev,(amd_iommu_invalid_ppr_cb)kcl_iommu_invalid_pri_request);
if(ret)
{
KCL_DEBUG_ERROR("register invalid PPR call back failed, pid:0x%x, group_leader id:0x%x, pasid:0x%x. ret:0x%x .\n", pid,group_leader->pid,pasid,ret);
return ret;
}
//register call back for invalidating a pasid contect .
ret = amd_iommu_set_invalidate_ctx_cb( pdev,(amd_iommu_invalidate_ctx)kcl_iommu_invalidate_ctx);
if(ret)
{
KCL_DEBUG_ERROR("register invalid pasid context call back failed, pid:0x%x, group_leader id:0x%x, pasid:0x%x. ret:0x%x .\n", pid,group_leader->pid,pasid,ret);
return ret;
}
#endif
return ret;
}
/** \brief Unbinds a previously bound execution context from a device
* \param pcidev [in] PCI device handle
* \param pasid [in] specified PASID.
*/
void ATI_API_CALL KCL_IOMMU_UnbindPasid( KCL_PCI_DevHandle pcidev,int pasid)
{
#ifdef IOMMUV2_SUPPORT
struct pci_dev* pdev = (struct pci_dev*)pcidev;
amd_iommu_unbind_pasid( pdev, pasid);
#endif
}
/** \check whether IOMMU is already initialized
* \ param pcidev [in] PCI device handle
*/
int ATI_API_CALL KCL_IOMMU_CheckInfo( KCL_PCI_DevHandle pcidev)
{
struct pci_dev* pdev = (struct pci_dev*)pcidev;
if ( pdev->dev.archdata.iommu )
{
return 1;
}
return 0;
}
static int ATI_API_CALL KCL_IOMMU_MapMMIO(struct acpi_table_header *table)
{
u8 *p = (u8 *)table, *end = (u8 *)table;
struct ivhd_header *h;
iommu_base = NULL;
end += table->length;
p += IVRS_HEADER_LENGTH;
while (p < end) {
h = (struct ivhd_header *)p;
switch (*p) {
case ACPI_IVHD_TYPE:
iommu_base = KCL_IO_MEM_Map(h->mmio_phys, MMIO_REGION_LENGTH, KCL_IOREMAPTYPE_NoCache);
break;
default:
break;
}
p += h->length;
}
return 0;
}
/** \brief set a physical address range not to be translated by IOMMU
* \param pa physical address
* \param length the size of this range
*/
void ATI_API_CALL KCL_IOMMU_SetExclusion(unsigned long long pa, unsigned long long length)
{
u64 start,limit;
u64 entry;
if (KCL_ACPI_ParseTable("IVRS", (KCL_ACPI_IntCallbackHandle)KCL_IOMMU_MapMMIO) != KCL_ACPI_OK)
{
return;
}
if (!iommu_base)
{
return;
}
start = pa & PAGE_MASK;
limit = (start + length) & PAGE_MASK;
entry = start | MMIO_EXCL_ENABLE_MASK | MMIO_EXCL_ALLOW_MASK;
KCL_IO_MEM_CopyToIO(iommu_base + MMIO_EXCL_BASE_OFFSET, &entry, sizeof(entry));
entry = limit;
KCL_IO_MEM_CopyToIO(iommu_base + MMIO_EXCL_LIMIT_OFFSET, &entry, sizeof(entry));
KCL_IO_MEM_Unmap(iommu_base);
}