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
|
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2026 Intel Corporation.
*/
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/pci_ids.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/workqueue.h>
#include "regs/xe_i2c_regs.h"
#include "xe_amc.h"
#include "xe_device.h"
#include "xe_i2c.h"
#include "xe_mmio.h"
/**
* DOC: Add-In Management Controller (AMC)
*
* Handler for the SMBus Alerts from the AMC. All the alerts from AMC will cause
* the device to be declared wedged.
*/
#define AMC_COMMAND 0x0f
#define AMC_GPU_I2C_ADDR 0x8f
#define AMC_VERSION_V1 0x01
#define AMC_DESTINATION_ID 12
#define AMC_SOURCE_ID 8
#define AMC_FLAGS 0xc8
#define AMC_MSG_TYPE 0x7e
#define AMC_GET_ALERT_REASON 0x01
enum xe_amc_alert {
AMC_ALERT_UNKNOWN,
AMC_ALERT_FW_DOWNLOAD,
AMC_ALERT_THERMAL_TRIP,
AMC_ALERT_OOB_REQUEST,
AMC_ALERT_OOB_RESET,
AMC_ALERT_CATERR,
};
static const char * const amc_alert[] = {
[AMC_ALERT_FW_DOWNLOAD] = "Firmware Download",
[AMC_ALERT_THERMAL_TRIP] = "Thermal Trip",
[AMC_ALERT_OOB_REQUEST] = "OOB Request",
[AMC_ALERT_OOB_RESET] = "OOB Reset",
[AMC_ALERT_CATERR] = "Catastrophic",
};
struct xe_amc {
struct xe_i2c *i2c;
struct work_struct work;
};
struct amc_header {
u8 command;
u8 len;
u8 address;
u8 version;
u8 destination;
u8 source;
u8 flags;
} __packed;
struct amc_message {
u8 type;
u16 vendor;
u8 command;
} __packed;
struct amc_request {
struct amc_header header;
struct amc_message message;
u32 reserved;
} __packed;
struct amc_response {
struct amc_header header;
struct amc_message message;
u8 error;
u8 value;
} __packed;
static const struct amc_request amc_get_alert_reason = {
.header = {
.command = AMC_COMMAND,
.len = sizeof(struct amc_request) - 2,
.address = AMC_GPU_I2C_ADDR,
.version = AMC_VERSION_V1,
.destination = AMC_DESTINATION_ID,
.source = AMC_SOURCE_ID,
.flags = AMC_FLAGS,
},
.message = {
.type = AMC_MSG_TYPE,
.vendor = htons(PCI_VENDOR_ID_INTEL),
.command = AMC_GET_ALERT_REASON,
},
};
static void xe_amc_work(struct work_struct *work)
{
const struct amc_request *request = &amc_get_alert_reason;
struct xe_amc *amc = from_work(amc, work, work);
u8 alert_reason = AMC_ALERT_UNKNOWN;
struct amc_response response;
struct i2c_client *client;
int ret;
client = amc->i2c->client[XE_I2C_CLIENT_AMC];
if (IS_ERR_OR_NULL(client))
goto out_reassert_interrupt;
ret = i2c_master_send(client, (u8 *)request, sizeof(*request));
if (ret < 0) {
dev_err(&client->dev, "failed to send request (%d)\n", ret);
goto out_reassert_interrupt;
}
/* AMC needs 20ms to generate the response. */
fsleep(20 * USEC_PER_MSEC);
ret = i2c_master_recv(client, (u8 *)&response, sizeof(response));
if (ret < 0) {
dev_err(&client->dev, "failed to read response (%d)\n", ret);
goto out_reassert_interrupt;
}
if (!response.header.len) {
dev_err(&client->dev, "empty response from AMC\n");
goto out_reassert_interrupt;
}
if (memcmp(&response.message, &request->message, sizeof(struct amc_message))) {
dev_err(&client->dev, "response does not match the request\n");
goto out_reassert_interrupt;
}
if (response.error) {
dev_err(&client->dev, "AMC error 0x%02x\n", response.error);
goto out_reassert_interrupt;
}
alert_reason = response.value;
dev_dbg(&client->dev, "Alert reason: %d\n", alert_reason);
out_reassert_interrupt:
xe_mmio_rmw32(amc->i2c->mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0);
switch (alert_reason) {
case AMC_ALERT_FW_DOWNLOAD:
case AMC_ALERT_THERMAL_TRIP:
case AMC_ALERT_OOB_REQUEST:
case AMC_ALERT_OOB_RESET:
case AMC_ALERT_CATERR:
dev_warn(amc->i2c->drm_dev, "AMC Alert: %s\n", amc_alert[alert_reason]);
xe_device_declare_wedged(i2c_client_to_xe_device(client));
break;
default:
dev_warn(amc->i2c->drm_dev, "unknown AMC alert: %d\n", alert_reason);
break;
}
}
void xe_amc_handle_alert(struct xe_i2c *i2c)
{
queue_work(system_long_wq, &i2c->amc->work);
}
int xe_amc_init(struct xe_i2c *i2c)
{
struct xe_amc *amc;
amc = kzalloc(sizeof(*amc), GFP_KERNEL);
if (!amc)
return -ENOMEM;
INIT_WORK(&amc->work, xe_amc_work);
i2c->amc = amc;
amc->i2c = i2c;
return 0;
}
void xe_amc_exit(struct xe_i2c *i2c)
{
if (i2c->amc) {
cancel_work_sync(&i2c->amc->work);
kfree(i2c->amc);
}
}
|