Users and sysadmins usually want to know what is the device utilization as a level 0 indication if they are efficiently using the device. Add a new opcode to the INFO IOCTL that will return the device utilization over the last period of 100-1000ms. The return value is 0-100, representing as percentage the total utilization rate.
Unified · Split
| @@ -178,11 +178,23 @@ static void cs_do_release(struct kref *ref) · 177 unmodified lines | ||
| 178 | 178 | |
| 179 | 179 | /* We also need to update CI for internal queues */ |
| 180 | 180 | if (cs->submitted) { |
| 181 | − int cs_cnt = atomic_dec_return(&hdev->cs_active_cnt); | |
| 181 | + hdev->asic_funcs->hw_queues_lock(hdev); | |
| 182 | 182 | |
| 183 | − WARN_ONCE((cs_cnt < 0), | |
| 184 | − "hl%d: error in CS active cnt %d\n", | |
| 185 | − hdev->id, cs_cnt); | |
| 183 | + hdev->cs_active_cnt--; | |
| 184 | + if (!hdev->cs_active_cnt) { | |
| 185 | + struct hl_device_idle_busy_ts *ts; | |
| 186 | + | |
| 187 | + ts = &hdev->idle_busy_ts_arr[hdev->idle_busy_ts_idx++]; | |
| 188 | + ts->busy_to_idle_ts = ktime_get(); | |
| 189 | + | |
| 190 | + if (hdev->idle_busy_ts_idx == HL_IDLE_BUSY_TS_ARR_SIZE) | |
| 191 | + hdev->idle_busy_ts_idx = 0; | |
| 192 | + } else if (hdev->cs_active_cnt < 0) { | |
| 193 | + dev_crit(hdev->dev, "CS active cnt %d is negative\n", | |
| 194 | + hdev->cs_active_cnt); | |
| 195 | + } | |
| 196 | + | |
| 197 | + hdev->asic_funcs->hw_queues_unlock(hdev); | |
| 186 | 198 | |
| 187 | 199 | hl_int_hw_queue_update_ci(cs); |
| 188 | 200 | |
| @@ -293,6 +293,14 @@ static int device_early_init(struct hl_device *hdev) · 292 unmodified lines | ||
| 293 | 293 | goto free_eq_wq; |
| 294 | 294 | } |
| 295 | 295 | |
| 296 | + hdev->idle_busy_ts_arr = kmalloc_array(HL_IDLE_BUSY_TS_ARR_SIZE, | |
| 297 | + sizeof(struct hl_device_idle_busy_ts), | |
| 298 | + (GFP_KERNEL | __GFP_ZERO)); | |
| 299 | + if (!hdev->idle_busy_ts_arr) { | |
| 300 | + rc = -ENOMEM; | |
| 301 | + goto free_chip_info; | |
| 302 | + } | |
| 303 | + | |
| 296 | 304 | hl_cb_mgr_init(&hdev->kernel_cb_mgr); |
| 297 | 305 | |
| 298 | 306 | mutex_init(&hdev->send_cpu_message_lock); |
| @@ -303,10 +311,11 @@ static int device_early_init(struct hl_device *hdev) · 4 unmodified lines | ||
| 303 | 311 | INIT_LIST_HEAD(&hdev->fpriv_list); |
| 304 | 312 | mutex_init(&hdev->fpriv_list_lock); |
| 305 | 313 | atomic_set(&hdev->in_reset, 0); |
| 306 | − atomic_set(&hdev->cs_active_cnt, 0); | |
| 307 | 314 | |
| 308 | 315 | return 0; |
| 309 | 316 | |
| 317 | +free_chip_info: | |
| 318 | + kfree(hdev->hl_chip_info); | |
| 310 | 319 | free_eq_wq: |
| 311 | 320 | destroy_workqueue(hdev->eq_wq); |
| 312 | 321 | free_cq_wq: |
| @@ -336,6 +345,7 @@ static void device_early_fini(struct hl_device *hdev) · 23 unmodified lines | ||
| 336 | 345 | |
| 337 | 346 | hl_cb_mgr_fini(hdev, &hdev->kernel_cb_mgr); |
| 338 | 347 | |
| 348 | + kfree(hdev->idle_busy_ts_arr); | |
| 339 | 349 | kfree(hdev->hl_chip_info); |
| 340 | 350 | |
| 341 | 351 | destroy_workqueue(hdev->eq_wq); |
| @@ -451,6 +461,102 @@ static void device_late_fini(struct hl_device *hdev) · 109 unmodified lines | ||
| 451 | 461 | hdev->late_init_done = false; |
| 452 | 462 | } |
| 453 | 463 | |
| 464 | +uint32_t hl_device_utilization(struct hl_device *hdev, uint32_t period_ms) | |
| 465 | +{ | |
| 466 | + struct hl_device_idle_busy_ts *ts; | |
| 467 | + ktime_t zero_ktime, curr = ktime_get(); | |
| 468 | + u32 overlap_cnt = 0, last_index = hdev->idle_busy_ts_idx; | |
| 469 | + s64 period_us, last_start_us, last_end_us, last_busy_time_us, | |
| 470 | + total_busy_time_us = 0, total_busy_time_ms; | |
| 471 | + | |
| 472 | + zero_ktime = ktime_set(0, 0); | |
| 473 | + period_us = period_ms * USEC_PER_MSEC; | |
| 474 | + ts = &hdev->idle_busy_ts_arr[last_index]; | |
| 475 | + | |
| 476 | + /* check case that device is currently in idle */ | |
| 477 | + if (!ktime_compare(ts->busy_to_idle_ts, zero_ktime) && | |
| 478 | + !ktime_compare(ts->idle_to_busy_ts, zero_ktime)) { | |
| 479 | + | |
| 480 | + last_index--; | |
| 481 | + /* Handle case idle_busy_ts_idx was 0 */ | |
| 482 | + if (last_index > HL_IDLE_BUSY_TS_ARR_SIZE) | |
| 483 | + last_index = HL_IDLE_BUSY_TS_ARR_SIZE - 1; | |
| 484 | + | |
| 485 | + ts = &hdev->idle_busy_ts_arr[last_index]; | |
| 486 | + } | |
| 487 | + | |
| 488 | + while (overlap_cnt < HL_IDLE_BUSY_TS_ARR_SIZE) { | |
| 489 | + /* Check if we are in last sample case. i.e. if the sample | |
| 490 | + * begun before the sampling period. This could be a real | |
| 491 | + * sample or 0 so need to handle both cases | |
| 492 | + */ | |
| 493 | + last_start_us = ktime_to_us( | |
| 494 | + ktime_sub(curr, ts->idle_to_busy_ts)); | |
| 495 | + | |
| 496 | + if (last_start_us > period_us) { | |
| 497 | + | |
| 498 | + /* First check two cases: | |
| 499 | + * 1. If the device is currently busy | |
| 500 | + * 2. If the device was idle during the whole sampling | |
| 501 | + * period | |
| 502 | + */ | |
| 503 | + | |
| 504 | + if (!ktime_compare(ts->busy_to_idle_ts, zero_ktime)) { | |
| 505 | + /* Check if the device is currently busy */ | |
| 506 | + if (ktime_compare(ts->idle_to_busy_ts, | |
| 507 | + zero_ktime)) | |
| 508 | + return 100; | |
| 509 | + | |
| 510 | + /* We either didn't have any activity or we | |
| 511 | + * reached an entry which is 0. Either way, | |
| 512 | + * exit and return what was accumulated so far | |
| 513 | + */ | |
| 514 | + break; | |
| 515 | + } | |
| 516 | + | |
| 517 | + /* If sample has finished, check it is relevant */ | |
| 518 | + last_end_us = ktime_to_us( | |
| 519 | + ktime_sub(curr, ts->busy_to_idle_ts)); | |
| 520 | + | |
| 521 | + if (last_end_us > period_us) | |
| 522 | + break; | |
| 523 | + | |
| 524 | + /* It is relevant so add it but with adjustment */ | |
| 525 | + last_busy_time_us = ktime_to_us( | |
| 526 | + ktime_sub(ts->busy_to_idle_ts, | |
| 527 | + ts->idle_to_busy_ts)); | |
| 528 | + total_busy_time_us += last_busy_time_us - | |
| 529 | + (last_start_us - period_us); | |
| 530 | + break; | |
| 531 | + } | |
| 532 | + | |
| 533 | + /* Check if the sample is finished or still open */ | |
| 534 | + if (ktime_compare(ts->busy_to_idle_ts, zero_ktime)) | |
| 535 | + last_busy_time_us = ktime_to_us( | |
| 536 | + ktime_sub(ts->busy_to_idle_ts, | |
| 537 | + ts->idle_to_busy_ts)); | |
| 538 | + else | |
| 539 | + last_busy_time_us = ktime_to_us( | |
| 540 | + ktime_sub(curr, ts->idle_to_busy_ts)); | |
| 541 | + | |
| 542 | + total_busy_time_us += last_busy_time_us; | |
| 543 | + | |
| 544 | + last_index--; | |
| 545 | + /* Handle case idle_busy_ts_idx was 0 */ | |
| 546 | + if (last_index > HL_IDLE_BUSY_TS_ARR_SIZE) | |
| 547 | + last_index = HL_IDLE_BUSY_TS_ARR_SIZE - 1; | |
| 548 | + | |
| 549 | + ts = &hdev->idle_busy_ts_arr[last_index]; | |
| 550 | + | |
| 551 | + overlap_cnt++; | |
| 552 | + } | |
| 553 | + | |
| 554 | + total_busy_time_ms = DIV_ROUND_UP_ULL(total_busy_time_us, | |
| 555 | + USEC_PER_MSEC); | |
| 556 | + | |
| 557 | + return DIV_ROUND_UP_ULL(total_busy_time_ms * 100, period_ms); | |
| 558 | +} | |
| 559 | + | |
| 454 | 560 | /* |
| 455 | 561 | * hl_device_set_frequency - set the frequency of the device |
| 456 | 562 | * |
| @@ -808,6 +914,14 @@ again: · 351 unmodified lines | ||
| 808 | 914 | for (i = 0 ; i < hdev->asic_prop.completion_queues_count ; i++) |
| 809 | 915 | hl_cq_reset(hdev, &hdev->completion_queue[i]); |
| 810 | 916 | |
| 917 | + hdev->idle_busy_ts_idx = 0; | |
| 918 | + hdev->idle_busy_ts_arr[0].busy_to_idle_ts = ktime_set(0, 0); | |
| 919 | + hdev->idle_busy_ts_arr[0].idle_to_busy_ts = ktime_set(0, 0); | |
| 920 | + | |
| 921 | + if (hdev->cs_active_cnt) | |
| 922 | + dev_crit(hdev->dev, "CS active cnt %d is not 0 during reset\n", | |
| 923 | + hdev->cs_active_cnt); | |
| 924 | + | |
| 811 | 925 | mutex_lock(&hdev->fpriv_list_lock); |
| 812 | 926 | |
| 813 | 927 | /* Make sure the context switch phase will run again */ |
| @@ -45,6 +45,8 @@ · 44 unmodified lines | ||
| 45 | 45 | /* MUST BE POWER OF 2 and larger than 1 */ |
| 46 | 46 | #define HL_MAX_PENDING_CS 64 |
| 47 | 47 | |
| 48 | +#define HL_IDLE_BUSY_TS_ARR_SIZE 4096 | |
| 49 | + | |
| 48 | 50 | /* Memory */ |
| 49 | 51 | #define MEM_HASH_TABLE_BITS 7 /* 1 << 7 buckets */ |
| 50 | 52 | |
| @@ -1156,6 +1158,16 @@ struct hl_device_reset_work { · 1105 unmodified lines | ||
| 1156 | 1158 | struct hl_device *hdev; |
| 1157 | 1159 | }; |
| 1158 | 1160 | |
| 1161 | +/** | |
| 1162 | + * struct hl_device_idle_busy_ts - used for calculating device utilization rate. | |
| 1163 | + * @idle_to_busy_ts: timestamp where device changed from idle to busy. | |
| 1164 | + * @busy_to_idle_ts: timestamp where device changed from busy to idle. | |
| 1165 | + */ | |
| 1166 | +struct hl_device_idle_busy_ts { | |
| 1167 | + ktime_t idle_to_busy_ts; | |
| 1168 | + ktime_t busy_to_idle_ts; | |
| 1169 | +}; | |
| 1170 | + | |
| 1159 | 1171 | /** |
| 1160 | 1172 | * struct hl_device - habanalabs device structure. |
| 1161 | 1173 | * @pdev: pointer to PCI device, can be NULL in case of simulator device. |
| @@ -1203,19 +1215,22 @@ struct hl_device_reset_work { · 41 unmodified lines | ||
| 1203 | 1215 | * when a user opens the device |
| 1204 | 1216 | * @fpriv_list_lock: protects the fpriv_list |
| 1205 | 1217 | * @compute_ctx: current compute context executing. |
| 1218 | + * @idle_busy_ts_arr: array to hold time stamps of transitions from idle to busy | |
| 1219 | + * and vice-versa | |
| 1206 | 1220 | * @dram_used_mem: current DRAM memory consumption. |
| 1207 | 1221 | * @timeout_jiffies: device CS timeout value. |
| 1208 | 1222 | * @max_power: the max power of the device, as configured by the sysadmin. This |
| 1209 | 1223 | * value is saved so in case of hard-reset, KMD will restore this |
| 1210 | 1224 | * value and update the F/W after the re-initialization |
| 1211 | 1225 | * @in_reset: is device in reset flow. |
| 1226 | + * @curr_pll_profile: current PLL profile. | |
| 1212 | 1227 | * @cs_active_cnt: number of active command submissions on this device (active |
| 1213 | 1228 | * means already in H/W queues) |
| 1214 | − * @curr_pll_profile: current PLL profile. | |
| 1215 | 1229 | * @major: habanalabs KMD major. |
| 1216 | 1230 | * @high_pll: high PLL profile frequency. |
| 1217 | 1231 | * @soft_reset_cnt: number of soft reset since KMD loading. |
| 1218 | 1232 | * @hard_reset_cnt: number of hard reset since KMD loading. |
| 1233 | + * @idle_busy_ts_idx: index of current entry in idle_busy_ts_arr | |
| 1219 | 1234 | * @id: device minor. |
| 1220 | 1235 | * @id_control: minor of the control device |
| 1221 | 1236 | * @disabled: is device disabled. |
| @@ -1285,16 +1300,19 @@ struct hl_device { · 63 unmodified lines | ||
| 1285 | 1300 | |
| 1286 | 1301 | struct hl_ctx *compute_ctx; |
| 1287 | 1302 | |
| 1303 | + struct hl_device_idle_busy_ts *idle_busy_ts_arr; | |
| 1304 | + | |
| 1288 | 1305 | atomic64_t dram_used_mem; |
| 1289 | 1306 | u64 timeout_jiffies; |
| 1290 | 1307 | u64 max_power; |
| 1291 | 1308 | atomic_t in_reset; |
| 1292 | − atomic_t cs_active_cnt; | |
| 1293 | 1309 | enum hl_pll_frequency curr_pll_profile; |
| 1310 | + int cs_active_cnt; | |
| 1294 | 1311 | u32 major; |
| 1295 | 1312 | u32 high_pll; |
| 1296 | 1313 | u32 soft_reset_cnt; |
| 1297 | 1314 | u32 hard_reset_cnt; |
| 1315 | + u32 idle_busy_ts_idx; | |
| 1298 | 1316 | u16 id; |
| 1299 | 1317 | u16 id_control; |
| 1300 | 1318 | u8 disabled; |
| @@ -1457,6 +1475,7 @@ int hl_device_reset(struct hl_device *hdev, bool hard_reset, · 156 unmodified lines | ||
| 1457 | 1475 | void hl_hpriv_get(struct hl_fpriv *hpriv); |
| 1458 | 1476 | void hl_hpriv_put(struct hl_fpriv *hpriv); |
| 1459 | 1477 | int hl_device_set_frequency(struct hl_device *hdev, enum hl_pll_frequency freq); |
| 1478 | +uint32_t hl_device_utilization(struct hl_device *hdev, uint32_t period_ms); | |
| 1460 | 1479 | |
| 1461 | 1480 | int hl_build_hwmon_channel_info(struct hl_device *hdev, |
| 1462 | 1481 | struct armcp_sensor *sensors_arr); |
| @@ -197,6 +197,29 @@ out: · 196 unmodified lines | ||
| 197 | 197 | return rc; |
| 198 | 198 | } |
| 199 | 199 | |
| 200 | +static int device_utilization(struct hl_device *hdev, struct hl_info_args *args) | |
| 201 | +{ | |
| 202 | + struct hl_info_device_utilization device_util = {0}; | |
| 203 | + u32 max_size = args->return_size; | |
| 204 | + void __user *out = (void __user *) (uintptr_t) args->return_pointer; | |
| 205 | + | |
| 206 | + if ((!max_size) || (!out)) | |
| 207 | + return -EINVAL; | |
| 208 | + | |
| 209 | + if ((args->period_ms < 100) || (args->period_ms > 1000) || | |
| 210 | + (args->period_ms % 100)) { | |
| 211 | + dev_err(hdev->dev, | |
| 212 | + "period %u must be between 100 - 1000 and must be divisible by 100\n", | |
| 213 | + args->period_ms); | |
| 214 | + return -EINVAL; | |
| 215 | + } | |
| 216 | + | |
| 217 | + device_util.utilization = hl_device_utilization(hdev, args->period_ms); | |
| 218 | + | |
| 219 | + return copy_to_user(out, &device_util, | |
| 220 | + min((size_t) max_size, sizeof(device_util))) ? -EFAULT : 0; | |
| 221 | +} | |
| 222 | + | |
| 200 | 223 | static int _hl_info_ioctl(struct hl_fpriv *hpriv, void *data, |
| 201 | 224 | struct device *dev) |
| 202 | 225 | { |
| @@ -239,6 +262,10 @@ static int _hl_info_ioctl(struct hl_fpriv *hpriv, void *data, · 36 unmodified lines | ||
| 239 | 262 | rc = hw_idle(hdev, args); |
| 240 | 263 | break; |
| 241 | 264 | |
| 265 | + case HL_INFO_DEVICE_UTILIZATION: | |
| 266 | + rc = device_utilization(hdev, args); | |
| 267 | + break; | |
| 268 | + | |
| 242 | 269 | default: |
| 243 | 270 | dev_err(dev, "Invalid request %d\n", args->op); |
| 244 | 271 | rc = -ENOTTY; |
| @@ -364,7 +364,13 @@ int hl_hw_queue_schedule_cs(struct hl_cs *cs) · 363 unmodified lines | ||
| 364 | 364 | spin_unlock(&hdev->hw_queues_mirror_lock); |
| 365 | 365 | } |
| 366 | 366 | |
| 367 | − atomic_inc(&hdev->cs_active_cnt); | |
| 367 | + if (!hdev->cs_active_cnt++) { | |
| 368 | + struct hl_device_idle_busy_ts *ts; | |
| 369 | + | |
| 370 | + ts = &hdev->idle_busy_ts_arr[hdev->idle_busy_ts_idx]; | |
| 371 | + ts->busy_to_idle_ts = ktime_set(0, 0); | |
| 372 | + ts->idle_to_busy_ts = ktime_get(); | |
| 373 | + } | |
| 368 | 374 | |
| 369 | 375 | list_for_each_entry_safe(job, tmp, &cs->job_list, cs_node) |
| 370 | 376 | if (job->ext_queue) |
| @@ -38,4 +38,6 @@ · 37 unmodified lines | ||
| 38 | 38 | |
| 39 | 39 | #define TPC_MAX_NUM 8 |
| 40 | 40 | |
| 41 | +#define MME_MAX_NUM 1 | |
| 42 | + | |
| 41 | 43 | #endif /* GOYA_H */ |
| @@ -77,22 +77,29 @@ enum hl_device_status { · 76 unmodified lines | ||
| 77 | 77 | |
| 78 | 78 | /* Opcode for management ioctl |
| 79 | 79 | * |
| 80 | − * HW_IP_INFO - Receive information about different IP blocks in the | |
| 81 | − * device. | |
| 82 | − * HL_INFO_HW_EVENTS - Receive an array describing how many times each event | |
| 83 | − * occurred since the last hard reset. | |
| 84 | − * HL_INFO_DRAM_USAGE - Retrieve the dram usage inside the device and of the | |
| 85 | − * specific context. This is relevant only for GOYA device. | |
| 86 | − * HL_INFO_HW_IDLE - Retrieve information about the idle status of each | |
| 87 | − * internal engine. | |
| 80 | + * HW_IP_INFO - Receive information about different IP blocks in the | |
| 81 | + * device. | |
| 82 | + * HL_INFO_HW_EVENTS - Receive an array describing how many times each event | |
| 83 | + * occurred since the last hard reset. | |
| 84 | + * HL_INFO_DRAM_USAGE - Retrieve the dram usage inside the device and of the | |
| 85 | + * specific context. This is relevant only for devices | |
| 86 | + * where the dram is managed by the kernel driver | |
| 87 | + * HL_INFO_HW_IDLE - Retrieve information about the idle status of each | |
| 88 | + * internal engine. | |
| 88 | 89 | * HL_INFO_DEVICE_STATUS - Retrieve the device's status. This opcode doesn't |
| 89 | 90 | * require an open context. |
| 91 | + * HL_INFO_DEVICE_UTILIZATION - Retrieve the total utilization of the device | |
| 92 | + * over the last period specified by the user. | |
| 93 | + * The period can be between 100ms to 1s, in | |
| 94 | + * resolution of 100ms. The return value is a | |
| 95 | + * percentage of the utilization rate. | |
| 90 | 96 | */ |
| 91 | −#define HL_INFO_HW_IP_INFO 0 | |
| 92 | −#define HL_INFO_HW_EVENTS 1 | |
| 93 | −#define HL_INFO_DRAM_USAGE 2 | |
| 94 | −#define HL_INFO_HW_IDLE 3 | |
| 95 | −#define HL_INFO_DEVICE_STATUS 4 | |
| 97 | +#define HL_INFO_HW_IP_INFO 0 | |
| 98 | +#define HL_INFO_HW_EVENTS 1 | |
| 99 | +#define HL_INFO_DRAM_USAGE 2 | |
| 100 | +#define HL_INFO_HW_IDLE 3 | |
| 101 | +#define HL_INFO_DEVICE_STATUS 4 | |
| 102 | +#define HL_INFO_DEVICE_UTILIZATION 6 | |
| 96 | 103 | |
| 97 | 104 | #define HL_INFO_VERSION_MAX_LEN 128 |
| 98 | 105 | |
| @@ -134,6 +141,11 @@ struct hl_info_device_status { · 35 unmodified lines | ||
| 134 | 141 | __u32 pad; |
| 135 | 142 | }; |
| 136 | 143 | |
| 144 | +struct hl_info_device_utilization { | |
| 145 | + __u32 utilization; | |
| 146 | + __u32 pad; | |
| 147 | +}; | |
| 148 | + | |
| 137 | 149 | struct hl_info_args { |
| 138 | 150 | /* Location of relevant struct in userspace */ |
| 139 | 151 | __u64 return_pointer; |
| @@ -149,8 +161,15 @@ struct hl_info_args { · 9 unmodified lines | ||
| 149 | 161 | /* HL_INFO_* */ |
| 150 | 162 | __u32 op; |
| 151 | 163 | |
| 152 | − /* Context ID - Currently not in use */ | |
| 153 | − __u32 ctx_id; | |
| 164 | + union { | |
| 165 | + /* Context ID - Currently not in use */ | |
| 166 | + __u32 ctx_id; | |
| 167 | + /* Period value for utilization rate (100ms - 1000ms, in 100ms | |
| 168 | + * resolution. | |
| 169 | + */ | |
| 170 | + __u32 period_ms; | |
| 171 | + }; | |
| 172 | + | |
| 154 | 173 | __u32 pad; |
| 155 | 174 | }; |
| 156 | 175 | |