1 // SPDX-License-Identifier: MIT 2 /* 3 * Copyright © 2025 Intel Corporation 4 */ 5 6 #include <linux/kobject.h> 7 #include <linux/sysfs.h> 8 9 #include <drm/drm_managed.h> 10 11 #include "xe_assert.h" 12 #include "xe_pci_sriov.h" 13 #include "xe_pm.h" 14 #include "xe_sriov.h" 15 #include "xe_sriov_pf.h" 16 #include "xe_sriov_pf_control.h" 17 #include "xe_sriov_pf_helpers.h" 18 #include "xe_sriov_pf_provision.h" 19 #include "xe_sriov_pf_sysfs.h" 20 #include "xe_sriov_printk.h" 21 22 static int emit_choice(char *buf, int choice, const char * const *array, size_t size) 23 { 24 int pos = 0; 25 int n; 26 27 for (n = 0; n < size; n++) { 28 pos += sysfs_emit_at(buf, pos, "%s%s%s%s", 29 n ? " " : "", 30 n == choice ? "[" : "", 31 array[n], 32 n == choice ? "]" : ""); 33 } 34 pos += sysfs_emit_at(buf, pos, "\n"); 35 36 return pos; 37 } 38 39 /* 40 * /sys/bus/pci/drivers/xe/BDF/ 41 * : 42 * ├── sriov_admin/ 43 * ├── ... 44 * ├── .bulk_profile 45 * │ ├── exec_quantum_ms 46 * │ ├── preempt_timeout_us 47 * │ └── sched_priority 48 * ├── pf/ 49 * │ ├── ... 50 * │ ├── device -> ../../../BDF 51 * │ └── profile 52 * │ ├── exec_quantum_ms 53 * │ ├── preempt_timeout_us 54 * │ └── sched_priority 55 * ├── vf1/ 56 * │ ├── ... 57 * │ ├── device -> ../../../BDF.1 58 * │ ├── stop 59 * │ └── profile 60 * │ ├── exec_quantum_ms 61 * │ ├── preempt_timeout_us 62 * │ └── sched_priority 63 * ├── vf2/ 64 * : 65 * └── vfN/ 66 */ 67 68 struct xe_sriov_kobj { 69 struct kobject base; 70 struct xe_device *xe; 71 unsigned int vfid; 72 }; 73 #define to_xe_sriov_kobj(p) container_of_const((p), struct xe_sriov_kobj, base) 74 75 struct xe_sriov_dev_attr { 76 struct attribute attr; 77 ssize_t (*show)(struct xe_device *xe, char *buf); 78 ssize_t (*store)(struct xe_device *xe, const char *buf, size_t count); 79 }; 80 #define to_xe_sriov_dev_attr(p) container_of_const((p), struct xe_sriov_dev_attr, attr) 81 82 #define XE_SRIOV_DEV_ATTR(NAME) \ 83 struct xe_sriov_dev_attr xe_sriov_dev_attr_##NAME = \ 84 __ATTR(NAME, 0644, xe_sriov_dev_attr_##NAME##_show, xe_sriov_dev_attr_##NAME##_store) 85 86 #define XE_SRIOV_DEV_ATTR_RO(NAME) \ 87 struct xe_sriov_dev_attr xe_sriov_dev_attr_##NAME = \ 88 __ATTR(NAME, 0444, xe_sriov_dev_attr_##NAME##_show, NULL) 89 90 #define XE_SRIOV_DEV_ATTR_WO(NAME) \ 91 struct xe_sriov_dev_attr xe_sriov_dev_attr_##NAME = \ 92 __ATTR(NAME, 0200, NULL, xe_sriov_dev_attr_##NAME##_store) 93 94 struct xe_sriov_vf_attr { 95 struct attribute attr; 96 ssize_t (*show)(struct xe_device *xe, unsigned int vfid, char *buf); 97 ssize_t (*store)(struct xe_device *xe, unsigned int vfid, const char *buf, size_t count); 98 }; 99 #define to_xe_sriov_vf_attr(p) container_of_const((p), struct xe_sriov_vf_attr, attr) 100 101 #define XE_SRIOV_VF_ATTR(NAME) \ 102 struct xe_sriov_vf_attr xe_sriov_vf_attr_##NAME = \ 103 __ATTR(NAME, 0644, xe_sriov_vf_attr_##NAME##_show, xe_sriov_vf_attr_##NAME##_store) 104 105 #define XE_SRIOV_VF_ATTR_RO(NAME) \ 106 struct xe_sriov_vf_attr xe_sriov_vf_attr_##NAME = \ 107 __ATTR(NAME, 0444, xe_sriov_vf_attr_##NAME##_show, NULL) 108 109 #define XE_SRIOV_VF_ATTR_WO(NAME) \ 110 struct xe_sriov_vf_attr xe_sriov_vf_attr_##NAME = \ 111 __ATTR(NAME, 0200, NULL, xe_sriov_vf_attr_##NAME##_store) 112 113 /* device level attributes go here */ 114 115 #define DEFINE_SIMPLE_BULK_PROVISIONING_SRIOV_DEV_ATTR_WO(NAME, ITEM, TYPE) \ 116 \ 117 static ssize_t xe_sriov_dev_attr_##NAME##_store(struct xe_device *xe, \ 118 const char *buf, size_t count) \ 119 { \ 120 TYPE value; \ 121 int err; \ 122 \ 123 err = kstrto##TYPE(buf, 0, &value); \ 124 if (err) \ 125 return err; \ 126 \ 127 err = xe_sriov_pf_provision_bulk_apply_##ITEM(xe, value); \ 128 return err ?: count; \ 129 } \ 130 \ 131 static XE_SRIOV_DEV_ATTR_WO(NAME) 132 133 DEFINE_SIMPLE_BULK_PROVISIONING_SRIOV_DEV_ATTR_WO(exec_quantum_ms, eq, u32); 134 DEFINE_SIMPLE_BULK_PROVISIONING_SRIOV_DEV_ATTR_WO(preempt_timeout_us, pt, u32); 135 136 static const char * const sched_priority_names[] = { 137 [GUC_SCHED_PRIORITY_LOW] = "low", 138 [GUC_SCHED_PRIORITY_NORMAL] = "normal", 139 [GUC_SCHED_PRIORITY_HIGH] = "high", 140 }; 141 142 static bool sched_priority_change_allowed(unsigned int vfid) 143 { 144 /* As of today GuC FW allows to selectively change only the PF priority. */ 145 return vfid == PFID; 146 } 147 148 static bool sched_priority_high_allowed(unsigned int vfid) 149 { 150 /* As of today GuC FW allows to select 'high' priority only for the PF. */ 151 return vfid == PFID; 152 } 153 154 static bool sched_priority_bulk_high_allowed(struct xe_device *xe) 155 { 156 /* all VFs are equal - it's sufficient to check VF1 only */ 157 return sched_priority_high_allowed(VFID(1)); 158 } 159 160 static ssize_t xe_sriov_dev_attr_sched_priority_store(struct xe_device *xe, 161 const char *buf, size_t count) 162 { 163 size_t num_priorities = ARRAY_SIZE(sched_priority_names); 164 int match; 165 int err; 166 167 if (!sched_priority_bulk_high_allowed(xe)) 168 num_priorities--; 169 170 match = __sysfs_match_string(sched_priority_names, num_priorities, buf); 171 if (match < 0) 172 return -EINVAL; 173 174 err = xe_sriov_pf_provision_bulk_apply_priority(xe, match); 175 return err ?: count; 176 } 177 178 static XE_SRIOV_DEV_ATTR_WO(sched_priority); 179 180 static struct attribute *bulk_profile_dev_attrs[] = { 181 &xe_sriov_dev_attr_exec_quantum_ms.attr, 182 &xe_sriov_dev_attr_preempt_timeout_us.attr, 183 &xe_sriov_dev_attr_sched_priority.attr, 184 NULL 185 }; 186 187 static const struct attribute_group bulk_profile_dev_attr_group = { 188 .name = ".bulk_profile", 189 .attrs = bulk_profile_dev_attrs, 190 }; 191 192 static const struct attribute_group *xe_sriov_dev_attr_groups[] = { 193 &bulk_profile_dev_attr_group, 194 NULL 195 }; 196 197 /* and VF-level attributes go here */ 198 199 #define DEFINE_SIMPLE_PROVISIONING_SRIOV_VF_ATTR(NAME, ITEM, TYPE, FORMAT) \ 200 static ssize_t xe_sriov_vf_attr_##NAME##_show(struct xe_device *xe, unsigned int vfid, \ 201 char *buf) \ 202 { \ 203 TYPE value = 0; \ 204 int err; \ 205 \ 206 err = xe_sriov_pf_provision_query_vf_##ITEM(xe, vfid, &value); \ 207 if (err) \ 208 return err; \ 209 \ 210 return sysfs_emit(buf, FORMAT, value); \ 211 } \ 212 \ 213 static ssize_t xe_sriov_vf_attr_##NAME##_store(struct xe_device *xe, unsigned int vfid, \ 214 const char *buf, size_t count) \ 215 { \ 216 TYPE value; \ 217 int err; \ 218 \ 219 err = kstrto##TYPE(buf, 0, &value); \ 220 if (err) \ 221 return err; \ 222 \ 223 err = xe_sriov_pf_provision_apply_vf_##ITEM(xe, vfid, value); \ 224 return err ?: count; \ 225 } \ 226 \ 227 static XE_SRIOV_VF_ATTR(NAME) 228 229 DEFINE_SIMPLE_PROVISIONING_SRIOV_VF_ATTR(exec_quantum_ms, eq, u32, "%u\n"); 230 DEFINE_SIMPLE_PROVISIONING_SRIOV_VF_ATTR(preempt_timeout_us, pt, u32, "%u\n"); 231 232 static ssize_t xe_sriov_vf_attr_sched_priority_show(struct xe_device *xe, unsigned int vfid, 233 char *buf) 234 { 235 size_t num_priorities = ARRAY_SIZE(sched_priority_names); 236 u32 priority; 237 int err; 238 239 err = xe_sriov_pf_provision_query_vf_priority(xe, vfid, &priority); 240 if (err) 241 return err; 242 243 if (!sched_priority_high_allowed(vfid)) 244 num_priorities--; 245 246 xe_assert(xe, priority < num_priorities); 247 return emit_choice(buf, priority, sched_priority_names, num_priorities); 248 } 249 250 static ssize_t xe_sriov_vf_attr_sched_priority_store(struct xe_device *xe, unsigned int vfid, 251 const char *buf, size_t count) 252 { 253 size_t num_priorities = ARRAY_SIZE(sched_priority_names); 254 int match; 255 int err; 256 257 if (!sched_priority_change_allowed(vfid)) 258 return -EOPNOTSUPP; 259 260 if (!sched_priority_high_allowed(vfid)) 261 num_priorities--; 262 263 match = __sysfs_match_string(sched_priority_names, num_priorities, buf); 264 if (match < 0) 265 return -EINVAL; 266 267 err = xe_sriov_pf_provision_apply_vf_priority(xe, vfid, match); 268 return err ?: count; 269 } 270 271 static XE_SRIOV_VF_ATTR(sched_priority); 272 273 static struct attribute *profile_vf_attrs[] = { 274 &xe_sriov_vf_attr_exec_quantum_ms.attr, 275 &xe_sriov_vf_attr_preempt_timeout_us.attr, 276 &xe_sriov_vf_attr_sched_priority.attr, 277 NULL 278 }; 279 280 static umode_t profile_vf_attr_is_visible(struct kobject *kobj, 281 struct attribute *attr, int index) 282 { 283 struct xe_sriov_kobj *vkobj = to_xe_sriov_kobj(kobj); 284 285 if (attr == &xe_sriov_vf_attr_sched_priority.attr && 286 !sched_priority_change_allowed(vkobj->vfid)) 287 return attr->mode & 0444; 288 289 return attr->mode; 290 } 291 292 static const struct attribute_group profile_vf_attr_group = { 293 .name = "profile", 294 .attrs = profile_vf_attrs, 295 .is_visible = profile_vf_attr_is_visible, 296 }; 297 298 #define DEFINE_SIMPLE_CONTROL_SRIOV_VF_ATTR(NAME) \ 299 \ 300 static ssize_t xe_sriov_vf_attr_##NAME##_store(struct xe_device *xe, unsigned int vfid, \ 301 const char *buf, size_t count) \ 302 { \ 303 bool yes; \ 304 int err; \ 305 \ 306 if (!vfid) \ 307 return -EPERM; \ 308 \ 309 err = kstrtobool(buf, &yes); \ 310 if (err) \ 311 return err; \ 312 if (!yes) \ 313 return count; \ 314 \ 315 err = xe_sriov_pf_control_##NAME##_vf(xe, vfid); \ 316 return err ?: count; \ 317 } \ 318 \ 319 static XE_SRIOV_VF_ATTR_WO(NAME) 320 321 DEFINE_SIMPLE_CONTROL_SRIOV_VF_ATTR(stop); 322 323 static struct attribute *control_vf_attrs[] = { 324 &xe_sriov_vf_attr_stop.attr, 325 NULL 326 }; 327 328 static umode_t control_vf_attr_is_visible(struct kobject *kobj, 329 struct attribute *attr, int index) 330 { 331 struct xe_sriov_kobj *vkobj = to_xe_sriov_kobj(kobj); 332 333 if (vkobj->vfid == PFID) 334 return 0; 335 336 return attr->mode; 337 } 338 339 static const struct attribute_group control_vf_attr_group = { 340 .attrs = control_vf_attrs, 341 .is_visible = control_vf_attr_is_visible, 342 }; 343 344 static const struct attribute_group *xe_sriov_vf_attr_groups[] = { 345 &profile_vf_attr_group, 346 &control_vf_attr_group, 347 NULL 348 }; 349 350 /* no user serviceable parts below */ 351 352 static struct kobject *create_xe_sriov_kobj(struct xe_device *xe, unsigned int vfid) 353 { 354 struct xe_sriov_kobj *vkobj; 355 356 xe_sriov_pf_assert_vfid(xe, vfid); 357 358 vkobj = kzalloc(sizeof(*vkobj), GFP_KERNEL); 359 if (!vkobj) 360 return NULL; 361 362 vkobj->xe = xe; 363 vkobj->vfid = vfid; 364 return &vkobj->base; 365 } 366 367 static void release_xe_sriov_kobj(struct kobject *kobj) 368 { 369 struct xe_sriov_kobj *vkobj = to_xe_sriov_kobj(kobj); 370 371 kfree(vkobj); 372 } 373 374 static ssize_t xe_sriov_dev_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) 375 { 376 struct xe_sriov_dev_attr *vattr = to_xe_sriov_dev_attr(attr); 377 struct xe_sriov_kobj *vkobj = to_xe_sriov_kobj(kobj); 378 struct xe_device *xe = vkobj->xe; 379 380 if (!vattr->show) 381 return -EPERM; 382 383 return vattr->show(xe, buf); 384 } 385 386 static ssize_t xe_sriov_dev_attr_store(struct kobject *kobj, struct attribute *attr, 387 const char *buf, size_t count) 388 { 389 struct xe_sriov_dev_attr *vattr = to_xe_sriov_dev_attr(attr); 390 struct xe_sriov_kobj *vkobj = to_xe_sriov_kobj(kobj); 391 struct xe_device *xe = vkobj->xe; 392 ssize_t ret; 393 394 if (!vattr->store) 395 return -EPERM; 396 397 xe_pm_runtime_get(xe); 398 ret = xe_sriov_pf_wait_ready(xe) ?: vattr->store(xe, buf, count); 399 xe_pm_runtime_put(xe); 400 401 return ret; 402 } 403 404 static ssize_t xe_sriov_vf_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) 405 { 406 struct xe_sriov_vf_attr *vattr = to_xe_sriov_vf_attr(attr); 407 struct xe_sriov_kobj *vkobj = to_xe_sriov_kobj(kobj); 408 struct xe_device *xe = vkobj->xe; 409 unsigned int vfid = vkobj->vfid; 410 411 xe_sriov_pf_assert_vfid(xe, vfid); 412 413 if (!vattr->show) 414 return -EPERM; 415 416 return vattr->show(xe, vfid, buf); 417 } 418 419 static ssize_t xe_sriov_vf_attr_store(struct kobject *kobj, struct attribute *attr, 420 const char *buf, size_t count) 421 { 422 struct xe_sriov_vf_attr *vattr = to_xe_sriov_vf_attr(attr); 423 struct xe_sriov_kobj *vkobj = to_xe_sriov_kobj(kobj); 424 struct xe_device *xe = vkobj->xe; 425 unsigned int vfid = vkobj->vfid; 426 ssize_t ret; 427 428 xe_sriov_pf_assert_vfid(xe, vfid); 429 430 if (!vattr->store) 431 return -EPERM; 432 433 xe_pm_runtime_get(xe); 434 ret = xe_sriov_pf_wait_ready(xe) ?: vattr->store(xe, vfid, buf, count); 435 xe_pm_runtime_get(xe); 436 437 return ret; 438 } 439 440 static const struct sysfs_ops xe_sriov_dev_sysfs_ops = { 441 .show = xe_sriov_dev_attr_show, 442 .store = xe_sriov_dev_attr_store, 443 }; 444 445 static const struct sysfs_ops xe_sriov_vf_sysfs_ops = { 446 .show = xe_sriov_vf_attr_show, 447 .store = xe_sriov_vf_attr_store, 448 }; 449 450 static const struct kobj_type xe_sriov_dev_ktype = { 451 .release = release_xe_sriov_kobj, 452 .sysfs_ops = &xe_sriov_dev_sysfs_ops, 453 .default_groups = xe_sriov_dev_attr_groups, 454 }; 455 456 static const struct kobj_type xe_sriov_vf_ktype = { 457 .release = release_xe_sriov_kobj, 458 .sysfs_ops = &xe_sriov_vf_sysfs_ops, 459 .default_groups = xe_sriov_vf_attr_groups, 460 }; 461 462 static int pf_sysfs_error(struct xe_device *xe, int err, const char *what) 463 { 464 if (IS_ENABLED(CONFIG_DRM_XE_DEBUG)) 465 xe_sriov_dbg(xe, "Failed to setup sysfs %s (%pe)\n", what, ERR_PTR(err)); 466 return err; 467 } 468 469 static void pf_sysfs_note(struct xe_device *xe, int err, const char *what) 470 { 471 xe_sriov_dbg(xe, "Failed to setup sysfs %s (%pe)\n", what, ERR_PTR(err)); 472 } 473 474 static void action_put_kobject(void *arg) 475 { 476 struct kobject *kobj = arg; 477 478 kobject_put(kobj); 479 } 480 481 static int pf_setup_root(struct xe_device *xe) 482 { 483 struct kobject *parent = &xe->drm.dev->kobj; 484 struct kobject *root; 485 int err; 486 487 root = create_xe_sriov_kobj(xe, PFID); 488 if (!root) 489 return pf_sysfs_error(xe, -ENOMEM, "root obj"); 490 491 err = devm_add_action_or_reset(xe->drm.dev, action_put_kobject, root); 492 if (err) 493 return pf_sysfs_error(xe, err, "root action"); 494 495 err = kobject_init_and_add(root, &xe_sriov_dev_ktype, parent, "sriov_admin"); 496 if (err) 497 return pf_sysfs_error(xe, err, "root init"); 498 499 xe_assert(xe, IS_SRIOV_PF(xe)); 500 xe_assert(xe, !xe->sriov.pf.sysfs.root); 501 xe->sriov.pf.sysfs.root = root; 502 return 0; 503 } 504 505 static int pf_setup_tree(struct xe_device *xe) 506 { 507 unsigned int totalvfs = xe_sriov_pf_get_totalvfs(xe); 508 struct kobject *root, *kobj; 509 unsigned int n; 510 int err; 511 512 xe_assert(xe, IS_SRIOV_PF(xe)); 513 root = xe->sriov.pf.sysfs.root; 514 515 for (n = 0; n <= totalvfs; n++) { 516 kobj = create_xe_sriov_kobj(xe, VFID(n)); 517 if (!kobj) 518 return pf_sysfs_error(xe, -ENOMEM, "tree obj"); 519 520 err = devm_add_action_or_reset(xe->drm.dev, action_put_kobject, root); 521 if (err) 522 return pf_sysfs_error(xe, err, "tree action"); 523 524 if (n) 525 err = kobject_init_and_add(kobj, &xe_sriov_vf_ktype, 526 root, "vf%u", n); 527 else 528 err = kobject_init_and_add(kobj, &xe_sriov_vf_ktype, 529 root, "pf"); 530 if (err) 531 return pf_sysfs_error(xe, err, "tree init"); 532 533 xe_assert(xe, !xe->sriov.pf.vfs[n].kobj); 534 xe->sriov.pf.vfs[n].kobj = kobj; 535 } 536 537 return 0; 538 } 539 540 static void action_rm_device_link(void *arg) 541 { 542 struct kobject *kobj = arg; 543 544 sysfs_remove_link(kobj, "device"); 545 } 546 547 static int pf_link_pf_device(struct xe_device *xe) 548 { 549 struct kobject *kobj = xe->sriov.pf.vfs[PFID].kobj; 550 int err; 551 552 err = sysfs_create_link(kobj, &xe->drm.dev->kobj, "device"); 553 if (err) 554 return pf_sysfs_error(xe, err, "PF device link"); 555 556 err = devm_add_action_or_reset(xe->drm.dev, action_rm_device_link, kobj); 557 if (err) 558 return pf_sysfs_error(xe, err, "PF unlink action"); 559 560 return 0; 561 } 562 563 /** 564 * xe_sriov_pf_sysfs_init() - Setup PF's SR-IOV sysfs tree. 565 * @xe: the PF &xe_device to setup sysfs 566 * 567 * This function will create additional nodes that will represent PF and VFs 568 * devices, each populated with SR-IOV Xe specific attributes. 569 * 570 * Return: 0 on success or a negative error code on failure. 571 */ 572 int xe_sriov_pf_sysfs_init(struct xe_device *xe) 573 { 574 int err; 575 576 err = pf_setup_root(xe); 577 if (err) 578 return err; 579 580 err = pf_setup_tree(xe); 581 if (err) 582 return err; 583 584 err = pf_link_pf_device(xe); 585 if (err) 586 return err; 587 588 return 0; 589 } 590 591 /** 592 * xe_sriov_pf_sysfs_link_vfs() - Add VF's links in SR-IOV sysfs tree. 593 * @xe: the &xe_device where to update sysfs 594 * @num_vfs: number of enabled VFs to link 595 * 596 * This function is specific for the PF driver. 597 * 598 * This function will add symbolic links between VFs represented in the SR-IOV 599 * sysfs tree maintained by the PF and enabled VF PCI devices. 600 * 601 * The @xe_sriov_pf_sysfs_unlink_vfs() shall be used to remove those links. 602 */ 603 void xe_sriov_pf_sysfs_link_vfs(struct xe_device *xe, unsigned int num_vfs) 604 { 605 unsigned int totalvfs = xe_sriov_pf_get_totalvfs(xe); 606 struct pci_dev *pf_pdev = to_pci_dev(xe->drm.dev); 607 struct pci_dev *vf_pdev = NULL; 608 unsigned int n; 609 int err; 610 611 xe_assert(xe, IS_SRIOV_PF(xe)); 612 xe_assert(xe, num_vfs <= totalvfs); 613 614 for (n = 1; n <= num_vfs; n++) { 615 vf_pdev = xe_pci_sriov_get_vf_pdev(pf_pdev, VFID(n)); 616 if (!vf_pdev) 617 return pf_sysfs_note(xe, -ENOENT, "VF link"); 618 619 err = sysfs_create_link(xe->sriov.pf.vfs[VFID(n)].kobj, 620 &vf_pdev->dev.kobj, "device"); 621 622 /* must balance xe_pci_sriov_get_vf_pdev() */ 623 pci_dev_put(vf_pdev); 624 625 if (err) 626 return pf_sysfs_note(xe, err, "VF link"); 627 } 628 } 629 630 /** 631 * xe_sriov_pf_sysfs_unlink_vfs() - Remove VF's links from SR-IOV sysfs tree. 632 * @xe: the &xe_device where to update sysfs 633 * @num_vfs: number of VFs to unlink 634 * 635 * This function shall be called only on the PF. 636 * This function will remove "device" links added by @xe_sriov_sysfs_link_vfs(). 637 */ 638 void xe_sriov_pf_sysfs_unlink_vfs(struct xe_device *xe, unsigned int num_vfs) 639 { 640 unsigned int n; 641 642 xe_assert(xe, IS_SRIOV_PF(xe)); 643 xe_assert(xe, num_vfs <= xe_sriov_pf_get_totalvfs(xe)); 644 645 for (n = 1; n <= num_vfs; n++) 646 sysfs_remove_link(xe->sriov.pf.vfs[VFID(n)].kobj, "device"); 647 } 648