aboutsummaryrefslogtreecommitdiff
path: root/drivers/base/smc_protected_mode_switcher/protected_mode_switcher_device.c
blob: 31c57cdb2c8894e01f6e893367a5fdbe3d0fcc1a (plain)
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
/*
 *
 * (C) COPYRIGHT 2017 ARM Limited. All rights reserved.
 *
 * This program is free software and is provided to you under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation, and any use by you of this program is subject to the terms
 * of such GNU licence.
 *
 * A copy of the licence is included with the program, and can also be obtained
 * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 *
 */



#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/compiler.h>

#include <linux/protected_mode_switcher.h>

/*
 * Protected Mode Switch
 */

#define SMC_FAST_CALL  (1 << 31)
#define SMC_64         (1 << 30)
#define SMC_OEN_OFFSET 24
#define SMC_OEN_SIP    (2 << SMC_OEN_OFFSET)

struct smc_protected_mode_device {
	u16 smc_fid_enable;
	u16 smc_fid_disable;
	struct device *dev;
};

asmlinkage u64 __invoke_protected_mode_switch_smc(u64, u64, u64, u64);

static u64 invoke_smc(u32 oen, u16 function_number, bool smc64,
		u64 arg0, u64 arg1, u64 arg2)
{
	u32 fid = 0;

	fid |= SMC_FAST_CALL; /* Bit 31: Fast call */
	if (smc64)
		fid |= SMC_64; /* Bit 30: 1=SMC64, 0=SMC32 */
	fid |= oen; /* Bit 29:24: OEN */
	/* Bit 23:16: Must be zero for fast calls */
	fid |= (function_number); /* Bit 15:0: function number */

	return __invoke_protected_mode_switch_smc(fid, arg0, arg1, arg2);
}

static int protected_mode_enable(struct protected_mode_device *protected_dev)
{
	struct smc_protected_mode_device *sdev = protected_dev->data;

	if (!sdev)
		/* Not supported */
		return -EINVAL;

	return invoke_smc(SMC_OEN_SIP,
			sdev->smc_fid_enable, false,
			0, 0, 0);

}

static int protected_mode_disable(struct protected_mode_device *protected_dev)
{
	struct smc_protected_mode_device *sdev = protected_dev->data;

	if (!sdev)
		/* Not supported */
		return -EINVAL;

	return invoke_smc(SMC_OEN_SIP,
			sdev->smc_fid_disable, false,
			0, 0, 0);
}


static int protected_mode_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct protected_mode_device *protected_dev;
	struct smc_protected_mode_device *sdev;
	u32 tmp = 0;

	protected_dev = devm_kzalloc(&pdev->dev, sizeof(*protected_dev),
			GFP_KERNEL);
	if (!protected_dev)
		return -ENOMEM;

	sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
	if (!sdev) {
		devm_kfree(&pdev->dev, protected_dev);
		return -ENOMEM;
	}

	protected_dev->data = sdev;
	protected_dev->ops.protected_mode_enable = protected_mode_enable;
	protected_dev->ops.protected_mode_disable = protected_mode_disable;
	sdev->dev = dev;

	if (!of_property_read_u32(dev->of_node, "arm,smc,protected_enable",
			&tmp))
		sdev->smc_fid_enable = tmp;

	if (!of_property_read_u32(dev->of_node, "arm,smc,protected_disable",
			&tmp))
		sdev->smc_fid_disable = tmp;

	/* Check older property names, for compatibility with outdated DTBs */
	if (!of_property_read_u32(dev->of_node, "arm,smc,secure_enable", &tmp))
		sdev->smc_fid_enable = tmp;

	if (!of_property_read_u32(dev->of_node, "arm,smc,secure_disable", &tmp))
		sdev->smc_fid_disable = tmp;

	platform_set_drvdata(pdev, protected_dev);

	dev_info(&pdev->dev, "Protected mode switcher %s loaded\n", pdev->name);
	dev_info(&pdev->dev, "SMC enable: 0x%x\n", sdev->smc_fid_enable);
	dev_info(&pdev->dev, "SMC disable: 0x%x\n", sdev->smc_fid_disable);

	return 0;
}

static int protected_mode_remove(struct platform_device *pdev)
{
	dev_info(&pdev->dev, "Protected mode switcher %s removed\n",
			pdev->name);

	return 0;
}

static const struct of_device_id protected_mode_dt_ids[] = {
	{ .compatible = "arm,smc-protected-mode-switcher" },
	{ .compatible = "arm,secure-mode-switcher" },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, protected_mode_dt_ids);

static struct platform_driver protected_mode_driver = {
	.probe = protected_mode_probe,
	.remove = protected_mode_remove,
	.driver = {
		.name = "smc-protected-mode-switcher",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(protected_mode_dt_ids),
	}
};

module_platform_driver(protected_mode_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ARM Ltd.");
MODULE_VERSION("1.0");