1 // SPDX-License-Identifier: GPL-2.0-only 2 3 #include <linux/ethtool.h> 4 #include <linux/jiffies.h> 5 6 #include "common.h" 7 #include "module_fw.h" 8 #include "cmis.h" 9 10 /* For accessing the LPL field on page 9Fh, the allowable length extension is 11 * min(i, 15) byte octets where i specifies the allowable additional number of 12 * byte octets in a READ or a WRITE. 13 */ 14 u32 ethtool_cmis_get_max_lpl_size(u8 num_of_byte_octs) 15 { 16 return 8 * (1 + min_t(u8, num_of_byte_octs, 15)); 17 } 18 19 /* For accessing the EPL field on page 9Fh, the allowable length extension is 20 * min(i, 255) byte octets where i specifies the allowable additional number of 21 * byte octets in a READ or a WRITE. 22 */ 23 u32 ethtool_cmis_get_max_epl_size(u8 num_of_byte_octs) 24 { 25 return 8 * (1 + min_t(u8, num_of_byte_octs, 255)); 26 } 27 28 void ethtool_cmis_cdb_compose_args(struct ethtool_cmis_cdb_cmd_args *args, 29 enum ethtool_cmis_cdb_cmd_id cmd, u8 *lpl, 30 u8 lpl_len, u8 *epl, u16 epl_len, 31 u16 max_duration, u8 read_write_len_ext, 32 u16 msleep_pre_rpl, u8 rpl_exp_len, u8 flags) 33 { 34 args->req.id = cpu_to_be16(cmd); 35 args->req.lpl_len = lpl_len; 36 if (lpl) { 37 memcpy(args->req.payload, lpl, args->req.lpl_len); 38 args->read_write_len_ext = 39 ethtool_cmis_get_max_lpl_size(read_write_len_ext); 40 } 41 if (epl) { 42 args->req.epl_len = cpu_to_be16(epl_len); 43 args->req.epl = epl; 44 args->read_write_len_ext = 45 ethtool_cmis_get_max_epl_size(read_write_len_ext); 46 } 47 48 args->max_duration = max_duration; 49 args->msleep_pre_rpl = msleep_pre_rpl; 50 args->rpl_exp_len = rpl_exp_len; 51 args->flags = flags; 52 args->err_msg = NULL; 53 } 54 55 void ethtool_cmis_page_init(struct ethtool_module_eeprom *page_data, 56 u8 page, u32 offset, u32 length) 57 { 58 page_data->page = page; 59 page_data->offset = offset; 60 page_data->length = length; 61 page_data->i2c_address = ETHTOOL_CMIS_CDB_PAGE_I2C_ADDR; 62 } 63 64 #define CMIS_REVISION_PAGE 0x00 65 #define CMIS_REVISION_OFFSET 0x01 66 67 struct cmis_rev_rpl { 68 u8 rev; 69 }; 70 71 static u8 cmis_rev_rpl_major(struct cmis_rev_rpl *rpl) 72 { 73 return rpl->rev >> 4; 74 } 75 76 static int cmis_rev_major_get(struct net_device *dev, u8 *rev_major) 77 { 78 const struct ethtool_ops *ops = dev->ethtool_ops; 79 struct ethtool_module_eeprom page_data = {0}; 80 struct netlink_ext_ack extack = {}; 81 struct cmis_rev_rpl rpl = {}; 82 int err; 83 84 ethtool_cmis_page_init(&page_data, CMIS_REVISION_PAGE, 85 CMIS_REVISION_OFFSET, sizeof(rpl)); 86 page_data.data = (u8 *)&rpl; 87 88 err = ops->get_module_eeprom_by_page(dev, &page_data, &extack); 89 if (err < 0) { 90 if (extack._msg) 91 netdev_err(dev, "%s\n", extack._msg); 92 return err; 93 } 94 95 *rev_major = cmis_rev_rpl_major(&rpl); 96 97 return 0; 98 } 99 100 #define CMIS_CDB_ADVERTISEMENT_PAGE 0x01 101 #define CMIS_CDB_ADVERTISEMENT_OFFSET 0xA3 102 103 /* Based on section 8.4.11 "CDB Messaging Support Advertisement" in CMIS 104 * standard revision 5.2. 105 */ 106 struct cmis_cdb_advert_rpl { 107 u8 inst_supported; 108 u8 read_write_len_ext; 109 u8 resv1; 110 u8 resv2; 111 }; 112 113 static u8 cmis_cdb_advert_rpl_inst_supported(struct cmis_cdb_advert_rpl *rpl) 114 { 115 return rpl->inst_supported >> 6; 116 } 117 118 static int cmis_cdb_advertisement_get(struct ethtool_cmis_cdb *cdb, 119 struct net_device *dev, 120 struct ethnl_module_fw_flash_ntf_params *ntf_params) 121 { 122 const struct ethtool_ops *ops = dev->ethtool_ops; 123 struct ethtool_module_eeprom page_data = {}; 124 struct cmis_cdb_advert_rpl rpl = {}; 125 struct netlink_ext_ack extack = {}; 126 int err; 127 128 ethtool_cmis_page_init(&page_data, CMIS_CDB_ADVERTISEMENT_PAGE, 129 CMIS_CDB_ADVERTISEMENT_OFFSET, sizeof(rpl)); 130 page_data.data = (u8 *)&rpl; 131 132 err = ops->get_module_eeprom_by_page(dev, &page_data, &extack); 133 if (err < 0) { 134 if (extack._msg) 135 netdev_err(dev, "%s\n", extack._msg); 136 return err; 137 } 138 139 if (!cmis_cdb_advert_rpl_inst_supported(&rpl)) { 140 ethnl_module_fw_flash_ntf_err(dev, ntf_params, 141 "CDB functionality is not supported", 142 NULL); 143 return -EOPNOTSUPP; 144 } 145 146 cdb->read_write_len_ext = rpl.read_write_len_ext; 147 148 return 0; 149 } 150 151 #define CMIS_PASSWORD_ENTRY_PAGE 0x00 152 #define CMIS_PASSWORD_ENTRY_OFFSET 0x7A 153 154 struct cmis_password_entry_pl { 155 __be32 password; 156 }; 157 158 /* See section 9.3.1 "CMD 0000h: Query Status" in CMIS standard revision 5.2. 159 * struct cmis_cdb_query_status_pl and struct cmis_cdb_query_status_rpl are 160 * structured layouts of the flat arrays, 161 * struct ethtool_cmis_cdb_request::payload and 162 * struct ethtool_cmis_cdb_rpl::payload respectively. 163 */ 164 struct cmis_cdb_query_status_pl { 165 u16 response_delay; 166 }; 167 168 struct cmis_cdb_query_status_rpl { 169 u8 length; 170 u8 status; 171 }; 172 173 static int 174 cmis_cdb_validate_password(struct ethtool_cmis_cdb *cdb, 175 struct net_device *dev, 176 const struct ethtool_module_fw_flash_params *params, 177 struct ethnl_module_fw_flash_ntf_params *ntf_params) 178 { 179 const struct ethtool_ops *ops = dev->ethtool_ops; 180 struct cmis_cdb_query_status_pl qs_pl = {0}; 181 struct ethtool_module_eeprom page_data = {}; 182 struct ethtool_cmis_cdb_cmd_args args = {}; 183 struct cmis_password_entry_pl pe_pl = {}; 184 struct cmis_cdb_query_status_rpl *rpl; 185 struct netlink_ext_ack extack = {}; 186 int err; 187 188 ethtool_cmis_page_init(&page_data, CMIS_PASSWORD_ENTRY_PAGE, 189 CMIS_PASSWORD_ENTRY_OFFSET, sizeof(pe_pl)); 190 page_data.data = (u8 *)&pe_pl; 191 192 pe_pl = *((struct cmis_password_entry_pl *)page_data.data); 193 pe_pl.password = params->password; 194 err = ops->set_module_eeprom_by_page(dev, &page_data, &extack); 195 if (err < 0) { 196 if (extack._msg) 197 netdev_err(dev, "%s\n", extack._msg); 198 return err; 199 } 200 201 ethtool_cmis_cdb_compose_args(&args, ETHTOOL_CMIS_CDB_CMD_QUERY_STATUS, 202 (u8 *)&qs_pl, sizeof(qs_pl), NULL, 0, 0, 203 cdb->read_write_len_ext, 1000, 204 sizeof(*rpl), 205 CDB_F_COMPLETION_VALID | CDB_F_STATUS_VALID); 206 207 err = ethtool_cmis_cdb_execute_cmd(dev, &args); 208 if (err < 0) { 209 ethnl_module_fw_flash_ntf_err(dev, ntf_params, 210 "Query Status command failed", 211 args.err_msg); 212 return err; 213 } 214 215 rpl = (struct cmis_cdb_query_status_rpl *)args.req.payload; 216 if (!rpl->length || !rpl->status) { 217 ethnl_module_fw_flash_ntf_err(dev, ntf_params, 218 "Password was not accepted", 219 NULL); 220 return -EINVAL; 221 } 222 223 return 0; 224 } 225 226 /* Some CDB commands asserts the CDB completion flag only from CMIS 227 * revision 5. Therefore, check the relevant validity flag only when 228 * the revision supports it. 229 */ 230 void ethtool_cmis_cdb_check_completion_flag(u8 cmis_rev, u8 *flags) 231 { 232 *flags |= cmis_rev >= 5 ? CDB_F_COMPLETION_VALID : 0; 233 } 234 235 #define CMIS_CDB_MODULE_FEATURES_RESV_DATA 34 236 237 /* See section 9.4.1 "CMD 0040h: Module Features" in CMIS standard revision 5.2. 238 * struct cmis_cdb_module_features_rpl is structured layout of the flat 239 * array, ethtool_cmis_cdb_rpl::payload. 240 */ 241 struct cmis_cdb_module_features_rpl { 242 u8 resv1[CMIS_CDB_MODULE_FEATURES_RESV_DATA]; 243 __be16 max_completion_time; 244 }; 245 246 static u16 247 cmis_cdb_module_features_completion_time(struct cmis_cdb_module_features_rpl *rpl) 248 { 249 return be16_to_cpu(rpl->max_completion_time); 250 } 251 252 static int cmis_cdb_module_features_get(struct ethtool_cmis_cdb *cdb, 253 struct net_device *dev, 254 struct ethnl_module_fw_flash_ntf_params *ntf_params) 255 { 256 struct ethtool_cmis_cdb_cmd_args args = {}; 257 struct cmis_cdb_module_features_rpl *rpl; 258 u8 flags = CDB_F_STATUS_VALID; 259 int err; 260 261 ethtool_cmis_cdb_check_completion_flag(cdb->cmis_rev, &flags); 262 ethtool_cmis_cdb_compose_args(&args, 263 ETHTOOL_CMIS_CDB_CMD_MODULE_FEATURES, 264 NULL, 0, NULL, 0, 0, 265 cdb->read_write_len_ext, 1000, 266 sizeof(*rpl), flags); 267 268 err = ethtool_cmis_cdb_execute_cmd(dev, &args); 269 if (err < 0) { 270 ethnl_module_fw_flash_ntf_err(dev, ntf_params, 271 "Module Features command failed", 272 args.err_msg); 273 return err; 274 } 275 276 rpl = (struct cmis_cdb_module_features_rpl *)args.req.payload; 277 cdb->max_completion_time = 278 cmis_cdb_module_features_completion_time(rpl); 279 280 return 0; 281 } 282 283 struct ethtool_cmis_cdb * 284 ethtool_cmis_cdb_init(struct net_device *dev, 285 const struct ethtool_module_fw_flash_params *params, 286 struct ethnl_module_fw_flash_ntf_params *ntf_params) 287 { 288 struct ethtool_cmis_cdb *cdb; 289 int err; 290 291 cdb = kzalloc(sizeof(*cdb), GFP_KERNEL); 292 if (!cdb) 293 return ERR_PTR(-ENOMEM); 294 295 err = cmis_rev_major_get(dev, &cdb->cmis_rev); 296 if (err < 0) 297 goto err; 298 299 if (cdb->cmis_rev < 4) { 300 ethnl_module_fw_flash_ntf_err(dev, ntf_params, 301 "CMIS revision doesn't support module firmware flashing", 302 NULL); 303 err = -EOPNOTSUPP; 304 goto err; 305 } 306 307 err = cmis_cdb_advertisement_get(cdb, dev, ntf_params); 308 if (err < 0) 309 goto err; 310 311 if (params->password_valid) { 312 err = cmis_cdb_validate_password(cdb, dev, params, ntf_params); 313 if (err < 0) 314 goto err; 315 } 316 317 err = cmis_cdb_module_features_get(cdb, dev, ntf_params); 318 if (err < 0) 319 goto err; 320 321 return cdb; 322 323 err: 324 ethtool_cmis_cdb_fini(cdb); 325 return ERR_PTR(err); 326 } 327 328 void ethtool_cmis_cdb_fini(struct ethtool_cmis_cdb *cdb) 329 { 330 kfree(cdb); 331 } 332 333 static bool is_completed(u8 data) 334 { 335 return !!(data & 0x40); 336 } 337 338 #define CMIS_CDB_STATUS_SUCCESS 0x01 339 340 static bool status_success(u8 data) 341 { 342 return data == CMIS_CDB_STATUS_SUCCESS; 343 } 344 345 #define CMIS_CDB_STATUS_FAIL 0x40 346 347 static bool status_fail(u8 data) 348 { 349 return data & CMIS_CDB_STATUS_FAIL; 350 } 351 352 struct cmis_wait_for_cond_rpl { 353 u8 state; 354 }; 355 356 static int 357 ethtool_cmis_module_poll(struct net_device *dev, 358 struct cmis_wait_for_cond_rpl *rpl, u32 offset, 359 bool (*cond_success)(u8), bool (*cond_fail)(u8)) 360 { 361 const struct ethtool_ops *ops = dev->ethtool_ops; 362 struct ethtool_module_eeprom page_data = {0}; 363 struct netlink_ext_ack extack = {}; 364 int err; 365 366 ethtool_cmis_page_init(&page_data, 0, offset, sizeof(rpl)); 367 page_data.data = (u8 *)rpl; 368 369 err = ops->get_module_eeprom_by_page(dev, &page_data, &extack); 370 if (err < 0) { 371 if (extack._msg) 372 netdev_err_once(dev, "%s\n", extack._msg); 373 return -EBUSY; 374 } 375 376 if ((*cond_success)(rpl->state)) 377 return 0; 378 379 if (*cond_fail && (*cond_fail)(rpl->state)) 380 return -EIO; 381 382 return -EBUSY; 383 } 384 385 int ethtool_cmis_wait_for_cond(struct net_device *dev, u8 flags, u8 flag, 386 u16 max_duration, u32 offset, 387 bool (*cond_success)(u8), bool (*cond_fail)(u8), 388 u8 *state) 389 { 390 struct cmis_wait_for_cond_rpl rpl = {}; 391 unsigned long end; 392 int err; 393 394 if (!(flags & flag)) 395 return 0; 396 397 if (max_duration == 0) 398 max_duration = U16_MAX; 399 400 end = jiffies + msecs_to_jiffies(max_duration); 401 do { 402 err = ethtool_cmis_module_poll(dev, &rpl, offset, cond_success, 403 cond_fail); 404 if (err != -EBUSY) 405 goto out; 406 407 msleep(20); 408 } while (time_before(jiffies, end)); 409 410 err = ethtool_cmis_module_poll(dev, &rpl, offset, cond_success, 411 cond_fail); 412 if (err == -EBUSY) 413 err = -ETIMEDOUT; 414 415 out: 416 *state = rpl.state; 417 return err; 418 } 419 420 #define CMIS_CDB_COMPLETION_FLAG_OFFSET 0x08 421 422 static int cmis_cdb_wait_for_completion(struct net_device *dev, 423 struct ethtool_cmis_cdb_cmd_args *args) 424 { 425 u8 flag; 426 int err; 427 428 /* Some vendors demand waiting time before checking completion flag 429 * in some CDB commands. 430 */ 431 msleep(args->msleep_pre_rpl); 432 433 err = ethtool_cmis_wait_for_cond(dev, args->flags, 434 CDB_F_COMPLETION_VALID, 435 args->max_duration, 436 CMIS_CDB_COMPLETION_FLAG_OFFSET, 437 is_completed, NULL, &flag); 438 if (err < 0) 439 args->err_msg = "Completion Flag did not set on time"; 440 441 return err; 442 } 443 444 #define CMIS_CDB_STATUS_OFFSET 0x25 445 446 static void cmis_cdb_status_fail_msg_get(u8 status, char **err_msg) 447 { 448 switch (status) { 449 case 0b10000001: 450 *err_msg = "CDB Status is in progress: Busy capturing command"; 451 break; 452 case 0b10000010: 453 *err_msg = 454 "CDB Status is in progress: Busy checking/validating command"; 455 break; 456 case 0b10000011: 457 *err_msg = "CDB Status is in progress: Busy executing"; 458 break; 459 case 0b01000000: 460 *err_msg = "CDB status failed: no specific failure"; 461 break; 462 case 0b01000010: 463 *err_msg = 464 "CDB status failed: Parameter range error or parameter not supported"; 465 break; 466 case 0b01000101: 467 *err_msg = "CDB status failed: CdbChkCode error"; 468 break; 469 case 0b01000110: 470 *err_msg = "CDB status failed: Password error"; 471 break; 472 default: 473 *err_msg = "Unknown failure reason"; 474 } 475 }; 476 477 static int cmis_cdb_wait_for_status(struct net_device *dev, 478 struct ethtool_cmis_cdb_cmd_args *args) 479 { 480 u8 status; 481 int err; 482 483 /* Some vendors demand waiting time before checking status in some 484 * CDB commands. 485 */ 486 msleep(args->msleep_pre_rpl); 487 488 err = ethtool_cmis_wait_for_cond(dev, args->flags, CDB_F_STATUS_VALID, 489 args->max_duration, 490 CMIS_CDB_STATUS_OFFSET, 491 status_success, status_fail, &status); 492 if (err < 0 && !args->err_msg) 493 cmis_cdb_status_fail_msg_get(status, &args->err_msg); 494 495 return err; 496 } 497 498 #define CMIS_CDB_REPLY_OFFSET 0x86 499 500 static int cmis_cdb_process_reply(struct net_device *dev, 501 struct ethtool_module_eeprom *page_data, 502 struct ethtool_cmis_cdb_cmd_args *args) 503 { 504 u8 rpl_hdr_len = sizeof(struct ethtool_cmis_cdb_rpl_hdr); 505 u8 rpl_exp_len = args->rpl_exp_len + rpl_hdr_len; 506 const struct ethtool_ops *ops = dev->ethtool_ops; 507 struct netlink_ext_ack extack = {}; 508 struct ethtool_cmis_cdb_rpl *rpl; 509 int err; 510 511 if (!args->rpl_exp_len) 512 return 0; 513 514 ethtool_cmis_page_init(page_data, ETHTOOL_CMIS_CDB_CMD_PAGE, 515 CMIS_CDB_REPLY_OFFSET, rpl_exp_len); 516 page_data->data = kmalloc(page_data->length, GFP_KERNEL); 517 if (!page_data->data) 518 return -ENOMEM; 519 520 err = ops->get_module_eeprom_by_page(dev, page_data, &extack); 521 if (err < 0) { 522 if (extack._msg) 523 netdev_err(dev, "%s\n", extack._msg); 524 goto out; 525 } 526 527 rpl = (struct ethtool_cmis_cdb_rpl *)page_data->data; 528 if ((args->rpl_exp_len > rpl->hdr.rpl_len + rpl_hdr_len) || 529 !rpl->hdr.rpl_chk_code) { 530 err = -EIO; 531 goto out; 532 } 533 534 args->req.lpl_len = rpl->hdr.rpl_len; 535 memcpy(args->req.payload, rpl->payload, args->req.lpl_len); 536 537 out: 538 kfree(page_data->data); 539 return err; 540 } 541 542 static int 543 __ethtool_cmis_cdb_execute_cmd(struct net_device *dev, 544 struct ethtool_module_eeprom *page_data, 545 u8 page, u32 offset, u32 length, void *data) 546 { 547 const struct ethtool_ops *ops = dev->ethtool_ops; 548 struct netlink_ext_ack extack = {}; 549 int err; 550 551 ethtool_cmis_page_init(page_data, page, offset, length); 552 page_data->data = kmemdup(data, page_data->length, GFP_KERNEL); 553 if (!page_data->data) 554 return -ENOMEM; 555 556 err = ops->set_module_eeprom_by_page(dev, page_data, &extack); 557 if (err < 0) { 558 if (extack._msg) 559 netdev_err(dev, "%s\n", extack._msg); 560 } 561 562 kfree(page_data->data); 563 return err; 564 } 565 566 #define CMIS_CDB_EPL_PAGE_START 0xA0 567 #define CMIS_CDB_EPL_PAGE_END 0xAF 568 #define CMIS_CDB_EPL_FW_BLOCK_OFFSET_START 128 569 #define CMIS_CDB_EPL_FW_BLOCK_OFFSET_END 255 570 571 static int 572 ethtool_cmis_cdb_execute_epl_cmd(struct net_device *dev, 573 struct ethtool_cmis_cdb_cmd_args *args, 574 struct ethtool_module_eeprom *page_data) 575 { 576 u16 epl_len = be16_to_cpu(args->req.epl_len); 577 u32 bytes_written = 0; 578 u8 page; 579 int err; 580 581 for (page = CMIS_CDB_EPL_PAGE_START; 582 page <= CMIS_CDB_EPL_PAGE_END && bytes_written < epl_len; page++) { 583 u16 offset = CMIS_CDB_EPL_FW_BLOCK_OFFSET_START; 584 585 while (offset <= CMIS_CDB_EPL_FW_BLOCK_OFFSET_END && 586 bytes_written < epl_len) { 587 u32 bytes_left = epl_len - bytes_written; 588 u16 space_left, bytes_to_write; 589 590 space_left = CMIS_CDB_EPL_FW_BLOCK_OFFSET_END - offset + 1; 591 bytes_to_write = min_t(u16, bytes_left, 592 min_t(u16, space_left, 593 args->read_write_len_ext)); 594 595 err = __ethtool_cmis_cdb_execute_cmd(dev, page_data, 596 page, offset, 597 bytes_to_write, 598 args->req.epl + bytes_written); 599 if (err < 0) 600 return err; 601 602 offset += bytes_to_write; 603 bytes_written += bytes_to_write; 604 } 605 } 606 return 0; 607 } 608 609 static u8 cmis_cdb_calc_checksum(const void *data, size_t size) 610 { 611 const u8 *bytes = (const u8 *)data; 612 u8 checksum = 0; 613 614 for (size_t i = 0; i < size; i++) 615 checksum += bytes[i]; 616 617 return ~checksum; 618 } 619 620 #define CMIS_CDB_CMD_ID_OFFSET 0x80 621 622 int ethtool_cmis_cdb_execute_cmd(struct net_device *dev, 623 struct ethtool_cmis_cdb_cmd_args *args) 624 { 625 struct ethtool_module_eeprom page_data = {}; 626 u32 offset; 627 int err; 628 629 args->req.chk_code = 630 cmis_cdb_calc_checksum(&args->req, 631 offsetof(struct ethtool_cmis_cdb_request, 632 epl)); 633 634 if (args->req.lpl_len > args->read_write_len_ext) { 635 args->err_msg = "LPL length is longer than CDB read write length extension allows"; 636 return -EINVAL; 637 } 638 639 /* According to the CMIS standard, there are two options to trigger the 640 * CDB commands. The default option is triggering the command by writing 641 * the CMDID bytes. Therefore, the command will be split to 2 calls: 642 * First, with everything except the CMDID field and then the CMDID 643 * field. 644 */ 645 offset = CMIS_CDB_CMD_ID_OFFSET + 646 offsetof(struct ethtool_cmis_cdb_request, body); 647 err = __ethtool_cmis_cdb_execute_cmd(dev, &page_data, 648 ETHTOOL_CMIS_CDB_CMD_PAGE, offset, 649 sizeof(args->req.body), 650 &args->req.body); 651 if (err < 0) 652 return err; 653 654 if (args->req.epl_len) { 655 err = ethtool_cmis_cdb_execute_epl_cmd(dev, args, &page_data); 656 if (err < 0) 657 return err; 658 } 659 660 offset = CMIS_CDB_CMD_ID_OFFSET + 661 offsetof(struct ethtool_cmis_cdb_request, id); 662 err = __ethtool_cmis_cdb_execute_cmd(dev, &page_data, 663 ETHTOOL_CMIS_CDB_CMD_PAGE, offset, 664 sizeof(args->req.id), 665 &args->req.id); 666 if (err < 0) 667 return err; 668 669 err = cmis_cdb_wait_for_completion(dev, args); 670 if (err < 0) 671 return err; 672 673 err = cmis_cdb_wait_for_status(dev, args); 674 if (err < 0) 675 return err; 676 677 return cmis_cdb_process_reply(dev, &page_data, args); 678 } 679