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 with self.assertRaises(OSError) as error: 142 get_nonce(self.d, self.signature) 143 self.assertEqual(error.exception.errno, 1) 144 145 def test_set_uid(self) -> None: 146 """set uid""" 147 with self.assertRaises(OSError) as error: 148 set_uid(self.d, self.uid, self.signature) 149 self.assertEqual(error.exception.errno, 1) 150 151 def test_get_param(self) -> None: 152 """fetch a parameter""" 153 with self.assertRaises(OSError) as error: 154 process_param(self.d, PARAM_GET_SOC_PWR_CUR, self.signature) 155 self.assertEqual(error.exception.errno, 1) 156 157 def test_set_param(self) -> None: 158 """set a parameter""" 159 with self.assertRaises(OSError) as error: 160 process_param(self.d, PARAM_SET_PWR_CAP, self.signature, 1000) 161 self.assertEqual(error.exception.errno, 1) 162 163 164class TestUnFusedSystem(DynamicBoostControlTest): 165 def setup_identity(self) -> None: 166 """sets up the identity of the caller""" 167 # if already authenticated these may fail 168 try: 169 get_nonce(self.d, None) 170 except PermissionError: 171 pass 172 try: 173 set_uid(self.d, self.uid, self.signature) 174 except BlockingIOError: 175 pass 176 try: 177 get_nonce(self.d, self.signature) 178 except PermissionError: 179 pass 180 181 def setUp(self) -> None: 182 if not os.path.exists(DEVICE_NODE): 183 self.skipTest("system is unsupported") 184 if system_is_secured(): 185 self.skipTest("system is fused") 186 super().setUp() 187 self.setup_identity() 188 time.sleep(SET_DELAY) 189 190 def test_get_valid_param(self) -> None: 191 """fetch all possible parameters""" 192 # SOC power 193 soc_power_max = process_param(self.d, PARAM_GET_SOC_PWR_MAX, self.signature) 194 soc_power_min = process_param(self.d, PARAM_GET_SOC_PWR_MIN, self.signature) 195 self.assertGreater(soc_power_max[0], soc_power_min[0]) 196 197 # fmax 198 fmax_max = process_param(self.d, PARAM_GET_FMAX_MAX, self.signature) 199 fmax_min = process_param(self.d, PARAM_GET_FMAX_MIN, self.signature) 200 self.assertGreater(fmax_max[0], fmax_min[0]) 201 202 # cap values 203 keys = { 204 "fmax-cap": PARAM_GET_FMAX_CAP, 205 "power-cap": PARAM_GET_PWR_CAP, 206 "current-temp": PARAM_GET_CURR_TEMP, 207 "soc-power-cur": PARAM_GET_SOC_PWR_CUR, 208 } 209 for k in keys: 210 result = process_param(self.d, keys[k], self.signature) 211 self.assertGreater(result[0], 0) 212 213 def test_get_invalid_param(self) -> None: 214 """fetch an invalid parameter""" 215 try: 216 set_uid(self.d, self.uid, self.signature) 217 except OSError: 218 pass 219 with self.assertRaises(OSError) as error: 220 process_param(self.d, (0xF,), self.signature) 221 self.assertEqual(error.exception.errno, 22) 222 223 def test_set_fmax(self) -> None: 224 """get/set fmax limit""" 225 # fetch current 226 original = process_param(self.d, PARAM_GET_FMAX_CAP, self.signature) 227 228 # set the fmax 229 target = original[0] - 100 230 process_param(self.d, PARAM_SET_FMAX_CAP, self.signature, target) 231 time.sleep(SET_DELAY) 232 new = process_param(self.d, PARAM_GET_FMAX_CAP, self.signature) 233 self.assertEqual(new[0], target) 234 235 # revert back to current 236 process_param(self.d, PARAM_SET_FMAX_CAP, self.signature, original[0]) 237 time.sleep(SET_DELAY) 238 cur = process_param(self.d, PARAM_GET_FMAX_CAP, self.signature) 239 self.assertEqual(cur[0], original[0]) 240 241 def test_set_power_cap(self) -> None: 242 """get/set power cap limit""" 243 # fetch current 244 original = process_param(self.d, PARAM_GET_PWR_CAP, self.signature) 245 246 # set the fmax 247 target = original[0] - 10 248 process_param(self.d, PARAM_SET_PWR_CAP, self.signature, target) 249 time.sleep(SET_DELAY) 250 new = process_param(self.d, PARAM_GET_PWR_CAP, self.signature) 251 self.assertEqual(new[0], target) 252 253 # revert back to current 254 process_param(self.d, PARAM_SET_PWR_CAP, self.signature, original[0]) 255 time.sleep(SET_DELAY) 256 cur = process_param(self.d, PARAM_GET_PWR_CAP, self.signature) 257 self.assertEqual(cur[0], original[0]) 258 259 def test_set_3d_graphics_mode(self) -> None: 260 """set/get 3d graphics mode""" 261 # these aren't currently implemented but may be some day 262 # they are *expected* to fail 263 with self.assertRaises(OSError) as error: 264 process_param(self.d, PARAM_GET_GFX_MODE, self.signature) 265 self.assertEqual(error.exception.errno, 2) 266 267 time.sleep(SET_DELAY) 268 269 with self.assertRaises(OSError) as error: 270 process_param(self.d, PARAM_SET_GFX_MODE, self.signature, 1) 271 self.assertEqual(error.exception.errno, 2) 272 273 274if __name__ == "__main__": 275 unittest.main() 276