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