xref: /openbmc/linux/tools/crypto/ccp/test_dbc.py (revision 6db6b729)
1#!/usr/bin/python3
2# SPDX-License-Identifier: GPL-2.0
3import unittest
4import os
5import time
6import glob
7from dbc import *
8
9# Artificial delay between set commands
10SET_DELAY = 0.5
11
12
13class invalid_param(ctypes.Structure):
14    _fields_ = [
15        ("data", ctypes.c_uint8),
16    ]
17
18
19def system_is_secured() -> bool:
20    fused_part = glob.glob("/sys/bus/pci/drivers/ccp/**/fused_part")[0]
21    if os.path.exists(fused_part):
22        with open(fused_part, "r") as r:
23            return int(r.read()) == 1
24    return True
25
26
27class DynamicBoostControlTest(unittest.TestCase):
28    def __init__(self, data) -> None:
29        self.d = None
30        self.signature = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
31        self.uid = "1111111111111111"
32        super().__init__(data)
33
34    def setUp(self) -> None:
35        self.d = open(DEVICE_NODE)
36        return super().setUp()
37
38    def tearDown(self) -> None:
39        if self.d:
40            self.d.close()
41        return super().tearDown()
42
43
44class TestUnsupportedSystem(DynamicBoostControlTest):
45    def setUp(self) -> None:
46        if os.path.exists(DEVICE_NODE):
47            self.skipTest("system is supported")
48        with self.assertRaises(FileNotFoundError) as error:
49            super().setUp()
50        self.assertEqual(error.exception.errno, 2)
51
52    def test_unauthenticated_nonce(self) -> None:
53        """fetch unauthenticated nonce"""
54        with self.assertRaises(ValueError) as error:
55            get_nonce(self.d, None)
56
57
58class TestInvalidIoctls(DynamicBoostControlTest):
59    def __init__(self, data) -> None:
60        self.data = invalid_param()
61        self.data.data = 1
62        super().__init__(data)
63
64    def setUp(self) -> None:
65        if not os.path.exists(DEVICE_NODE):
66            self.skipTest("system is unsupported")
67        return super().setUp()
68
69    def test_invalid_nonce_ioctl(self) -> None:
70        """tries to call get_nonce ioctl with invalid data structures"""
71
72        # 0x1 (get nonce), and invalid data
73        INVALID1 = IOWR(ord("D"), 0x01, invalid_param)
74        with self.assertRaises(OSError) as error:
75            fcntl.ioctl(self.d, INVALID1, self.data, True)
76        self.assertEqual(error.exception.errno, 22)
77
78    def test_invalid_setuid_ioctl(self) -> None:
79        """tries to call set_uid ioctl with invalid data structures"""
80
81        # 0x2 (set uid), and invalid data
82        INVALID2 = IOW(ord("D"), 0x02, invalid_param)
83        with self.assertRaises(OSError) as error:
84            fcntl.ioctl(self.d, INVALID2, self.data, True)
85        self.assertEqual(error.exception.errno, 22)
86
87    def test_invalid_setuid_rw_ioctl(self) -> None:
88        """tries to call set_uid ioctl with invalid data structures"""
89
90        # 0x2 as RW (set uid), and invalid data
91        INVALID3 = IOWR(ord("D"), 0x02, invalid_param)
92        with self.assertRaises(OSError) as error:
93            fcntl.ioctl(self.d, INVALID3, self.data, True)
94        self.assertEqual(error.exception.errno, 22)
95
96    def test_invalid_param_ioctl(self) -> None:
97        """tries to call param ioctl with invalid data structures"""
98        # 0x3 (param), and invalid data
99        INVALID4 = IOWR(ord("D"), 0x03, invalid_param)
100        with self.assertRaises(OSError) as error:
101            fcntl.ioctl(self.d, INVALID4, self.data, True)
102        self.assertEqual(error.exception.errno, 22)
103
104    def test_invalid_call_ioctl(self) -> None:
105        """tries to call the DBC ioctl with invalid data structures"""
106        # 0x4, and invalid data
107        INVALID5 = IOWR(ord("D"), 0x04, invalid_param)
108        with self.assertRaises(OSError) as error:
109            fcntl.ioctl(self.d, INVALID5, self.data, True)
110        self.assertEqual(error.exception.errno, 22)
111
112
113class TestInvalidSignature(DynamicBoostControlTest):
114    def setUp(self) -> None:
115        if not os.path.exists(DEVICE_NODE):
116            self.skipTest("system is unsupported")
117        if not system_is_secured():
118            self.skipTest("system is unfused")
119        return super().setUp()
120
121    def test_unauthenticated_nonce(self) -> None:
122        """fetch unauthenticated nonce"""
123        get_nonce(self.d, None)
124
125    def test_multiple_unauthenticated_nonce(self) -> None:
126        """ensure state machine always returns nonce"""
127        for count in range(0, 2):
128            get_nonce(self.d, None)
129
130    def test_authenticated_nonce(self) -> None:
131        """fetch authenticated nonce"""
132        with self.assertRaises(OSError) as error:
133            get_nonce(self.d, self.signature)
134        self.assertEqual(error.exception.errno, 1)
135
136    def test_set_uid(self) -> None:
137        """set uid"""
138        with self.assertRaises(OSError) as error:
139            set_uid(self.d, self.uid, self.signature)
140        self.assertEqual(error.exception.errno, 1)
141
142    def test_get_param(self) -> None:
143        """fetch a parameter"""
144        with self.assertRaises(OSError) as error:
145            process_param(self.d, PARAM_GET_SOC_PWR_CUR, self.signature)
146        self.assertEqual(error.exception.errno, 1)
147
148    def test_set_param(self) -> None:
149        """set a parameter"""
150        with self.assertRaises(OSError) as error:
151            process_param(self.d, PARAM_SET_PWR_CAP, self.signature, 1000)
152        self.assertEqual(error.exception.errno, 1)
153
154
155class TestUnFusedSystem(DynamicBoostControlTest):
156    def setup_identity(self) -> None:
157        """sets up the identity of the caller"""
158        # if already authenticated these may fail
159        try:
160            get_nonce(self.d, None)
161        except PermissionError:
162            pass
163        try:
164            set_uid(self.d, self.uid, self.signature)
165        except BlockingIOError:
166            pass
167        try:
168            get_nonce(self.d, self.signature)
169        except PermissionError:
170            pass
171
172    def setUp(self) -> None:
173        if not os.path.exists(DEVICE_NODE):
174            self.skipTest("system is unsupported")
175        if system_is_secured():
176            self.skipTest("system is fused")
177        super().setUp()
178        self.setup_identity()
179        time.sleep(SET_DELAY)
180
181    def test_get_valid_param(self) -> None:
182        """fetch all possible parameters"""
183        # SOC power
184        soc_power_max = process_param(self.d, PARAM_GET_SOC_PWR_MAX, self.signature)
185        soc_power_min = process_param(self.d, PARAM_GET_SOC_PWR_MIN, self.signature)
186        self.assertGreater(soc_power_max.parameter, soc_power_min.parameter)
187
188        # fmax
189        fmax_max = process_param(self.d, PARAM_GET_FMAX_MAX, self.signature)
190        fmax_min = process_param(self.d, PARAM_GET_FMAX_MIN, self.signature)
191        self.assertGreater(fmax_max.parameter, fmax_min.parameter)
192
193        # cap values
194        keys = {
195            "fmax-cap": PARAM_GET_FMAX_CAP,
196            "power-cap": PARAM_GET_PWR_CAP,
197            "current-temp": PARAM_GET_CURR_TEMP,
198            "soc-power-cur": PARAM_GET_SOC_PWR_CUR,
199        }
200        for k in keys:
201            result = process_param(self.d, keys[k], self.signature)
202            self.assertGreater(result.parameter, 0)
203
204    def test_get_invalid_param(self) -> None:
205        """fetch an invalid parameter"""
206        try:
207            set_uid(self.d, self.uid, self.signature)
208        except OSError:
209            pass
210        with self.assertRaises(OSError) as error:
211            process_param(self.d, (0xF,), self.signature)
212        self.assertEqual(error.exception.errno, 22)
213
214    def test_set_fmax(self) -> None:
215        """get/set fmax limit"""
216        # fetch current
217        original = process_param(self.d, PARAM_GET_FMAX_CAP, self.signature)
218
219        # set the fmax
220        target = original.parameter - 100
221        process_param(self.d, PARAM_SET_FMAX_CAP, self.signature, target)
222        time.sleep(SET_DELAY)
223        new = process_param(self.d, PARAM_GET_FMAX_CAP, self.signature)
224        self.assertEqual(new.parameter, target)
225
226        # revert back to current
227        process_param(self.d, PARAM_SET_FMAX_CAP, self.signature, original.parameter)
228        time.sleep(SET_DELAY)
229        cur = process_param(self.d, PARAM_GET_FMAX_CAP, self.signature)
230        self.assertEqual(cur.parameter, original.parameter)
231
232    def test_set_power_cap(self) -> None:
233        """get/set power cap limit"""
234        # fetch current
235        original = process_param(self.d, PARAM_GET_PWR_CAP, self.signature)
236
237        # set the fmax
238        target = original.parameter - 10
239        process_param(self.d, PARAM_SET_PWR_CAP, self.signature, target)
240        time.sleep(SET_DELAY)
241        new = process_param(self.d, PARAM_GET_PWR_CAP, self.signature)
242        self.assertEqual(new.parameter, target)
243
244        # revert back to current
245        process_param(self.d, PARAM_SET_PWR_CAP, self.signature, original.parameter)
246        time.sleep(SET_DELAY)
247        cur = process_param(self.d, PARAM_GET_PWR_CAP, self.signature)
248        self.assertEqual(cur.parameter, original.parameter)
249
250    def test_set_3d_graphics_mode(self) -> None:
251        """set/get 3d graphics mode"""
252        # these aren't currently implemented but may be some day
253        # they are *expected* to fail
254        with self.assertRaises(OSError) as error:
255            process_param(self.d, PARAM_GET_GFX_MODE, self.signature)
256        self.assertEqual(error.exception.errno, 2)
257
258        time.sleep(SET_DELAY)
259
260        with self.assertRaises(OSError) as error:
261            process_param(self.d, PARAM_SET_GFX_MODE, self.signature, 1)
262        self.assertEqual(error.exception.errno, 2)
263
264
265if __name__ == "__main__":
266    unittest.main()
267