aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c
blob: 89b042aecef3aa263ce371725b8bb877423d72cd (plain) (blame)
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
// SPDX-License-Identifier: GPL-2.0
/*
 * Intel Speed Select Interface: Mbox via MSR Interface
 * Copyright (c) 2019, Intel Corporation.
 * All rights reserved.
 *
 * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
 */

#include <linux/module.h>
#include <linux/cpuhotplug.h>
#include <linux/pci.h>
#include <linux/sched/signal.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/topology.h>
#include <linux/uaccess.h>
#include <uapi/linux/isst_if.h>
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>

#include "isst_if_common.h"

#define MSR_OS_MAILBOX_INTERFACE	0xB0
#define MSR_OS_MAILBOX_DATA		0xB1
#define MSR_OS_MAILBOX_BUSY_BIT		31

/*
 * Based on experiments count is never more than 1, as the MSR overhead
 * is enough to finish the command. So here this is the worst case number.
 */
#define OS_MAILBOX_RETRY_COUNT		3

static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter,
				 u32 command_data, u32 *response_data)
{
	u32 retries;
	u64 data;
	int ret;

	/* Poll for rb bit == 0 */
	retries = OS_MAILBOX_RETRY_COUNT;
	do {
		rdmsrl(MSR_OS_MAILBOX_INTERFACE, data);
		if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) {
			ret = -EBUSY;
			continue;
		}
		ret = 0;
		break;
	} while (--retries);

	if (ret)
		return ret;

	/* Write DATA register */
	wrmsrl(MSR_OS_MAILBOX_DATA, command_data);

	/* Write command register */
	data = BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT) |
		      (parameter & GENMASK_ULL(13, 0)) << 16 |
		      (sub_command << 8) |
		      command;
	wrmsrl(MSR_OS_MAILBOX_INTERFACE, data);

	/* Poll for rb bit == 0 */
	retries = OS_MAILBOX_RETRY_COUNT;
	do {
		rdmsrl(MSR_OS_MAILBOX_INTERFACE, data);
		if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) {
			ret = -EBUSY;
			continue;
		}

		if (data & 0xff)
			return -ENXIO;

		if (response_data) {
			rdmsrl(MSR_OS_MAILBOX_DATA, data);
			*response_data = data;
		}
		ret = 0;
		break;
	} while (--retries);

	return ret;
}

struct msrl_action {
	int err;
	struct isst_if_mbox_cmd *mbox_cmd;
};

/* revisit, smp_call_function_single should be enough for atomic mailbox! */
static void msrl_update_func(void *info)
{
	struct msrl_action *act = info;

	act->err = isst_if_send_mbox_cmd(act->mbox_cmd->command,
					 act->mbox_cmd->sub_command,
					 act->mbox_cmd->parameter,
					 act->mbox_cmd->req_data,
					 &act->mbox_cmd->resp_data);
}

static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume)
{
	struct msrl_action action;
	int ret;

	action.mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr;

	if (isst_if_mbox_cmd_invalid(action.mbox_cmd))
		return -EINVAL;

	if (isst_if_mbox_cmd_set_req(action.mbox_cmd) &&
	    !capable(CAP_SYS_ADMIN))
		return -EPERM;

	/*
	 * To complete mailbox command, we need to access two MSRs.
	 * So we don't want race to complete a mailbox transcation.
	 * Here smp_call ensures that msrl_update_func() has no race
	 * and also with wait flag, wait for completion.
	 * smp_call_function_single is using get_cpu() and put_cpu().
	 */
	ret = smp_call_function_single(action.mbox_cmd->logical_cpu,
				       msrl_update_func, &action, 1);
	if (ret)
		return ret;

	if (!action.err && !resume && isst_if_mbox_cmd_set_req(action.mbox_cmd))
		action.err = isst_store_cmd(action.mbox_cmd->command,
					    action.mbox_cmd->sub_command,
					    action.mbox_cmd->logical_cpu, 1,
					    action.mbox_cmd->parameter,
					    action.mbox_cmd->req_data);
	*write_only = 0;

	return action.err;
}


static int isst_pm_notify(struct notifier_block *nb,
			       unsigned long mode, void *_unused)
{
	switch (mode) {
	case PM_POST_HIBERNATION:
	case PM_POST_RESTORE:
	case PM_POST_SUSPEND:
		isst_resume_common();
		break;
	default:
		break;
	}
	return 0;
}

static struct notifier_block isst_pm_nb = {
	.notifier_call = isst_pm_notify,
};

#define ICPU(model)     { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, }

static const struct x86_cpu_id isst_if_cpu_ids[] = {
	ICPU(INTEL_FAM6_SKYLAKE_X),
	{}
};
MODULE_DEVICE_TABLE(x86cpu, isst_if_cpu_ids);

static int __init isst_if_mbox_init(void)
{
	struct isst_if_cmd_cb cb;
	const struct x86_cpu_id *id;
	u64 data;
	int ret;

	id = x86_match_cpu(isst_if_cpu_ids);
	if (!id)
		return -ENODEV;

	/* Check presence of mailbox MSRs */
	ret = rdmsrl_safe(MSR_OS_MAILBOX_INTERFACE, &data);
	if (ret)
		return ret;

	ret = rdmsrl_safe(MSR_OS_MAILBOX_DATA, &data);
	if (ret)
		return ret;

	memset(&cb, 0, sizeof(cb));
	cb.cmd_size = sizeof(struct isst_if_mbox_cmd);
	cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd);
	cb.cmd_callback = isst_if_mbox_proc_cmd;
	cb.owner = THIS_MODULE;
	ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb);
	if (ret)
		return ret;

	ret = register_pm_notifier(&isst_pm_nb);
	if (ret)
		isst_if_cdev_unregister(ISST_IF_DEV_MBOX);

	return ret;
}
module_init(isst_if_mbox_init)

static void __exit isst_if_mbox_exit(void)
{
	unregister_pm_notifier(&isst_pm_nb);
	isst_if_cdev_unregister(ISST_IF_DEV_MBOX);
}
module_exit(isst_if_mbox_exit)

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Intel speed select interface mailbox driver");