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