diff options
author | Dave Jiang <dave.jiang@intel.com> | 2017-10-30 16:22:20 -0400 |
---|---|---|
committer | Dan Williams <dan.j.williams@intel.com> | 2017-11-02 13:42:30 -0400 |
commit | 9fb1a1903345fea598f48277576a3589a972b72e (patch) | |
tree | f531856064348bfdb55e256a988e6b3d411ad026 /tools/testing | |
parent | aa9ad44a42b4cf4387f8ecddaf8e51707fdcda5a (diff) |
nfit_test: add error injection DSMs
Add nfit_test emulation for the new ACPI 6.2 error injectino DSMs.
This will allow unit tests to selectively inject the errors they wish to
test for.
Signed-off-by: Dave Jiang <dave.jiang@intel.com>
[vishal: Move injection functions to ND_CMD_CALL]
[vishal: Add support for the notification option]
[vishal: move an nfit_test private definition into a local header]
Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Diffstat (limited to 'tools/testing')
-rw-r--r-- | tools/testing/nvdimm/test/nfit.c | 187 | ||||
-rw-r--r-- | tools/testing/nvdimm/test/nfit_test.h | 5 |
2 files changed, 168 insertions, 24 deletions
diff --git a/tools/testing/nvdimm/test/nfit.c b/tools/testing/nvdimm/test/nfit.c index 3a3bad348d56..788b1a5d6c5a 100644 --- a/tools/testing/nvdimm/test/nfit.c +++ b/tools/testing/nvdimm/test/nfit.c | |||
@@ -168,8 +168,12 @@ struct nfit_test { | |||
168 | spinlock_t lock; | 168 | spinlock_t lock; |
169 | } ars_state; | 169 | } ars_state; |
170 | struct device *dimm_dev[NUM_DCR]; | 170 | struct device *dimm_dev[NUM_DCR]; |
171 | struct badrange badrange; | ||
172 | struct work_struct work; | ||
171 | }; | 173 | }; |
172 | 174 | ||
175 | static struct workqueue_struct *nfit_wq; | ||
176 | |||
173 | static struct nfit_test *to_nfit_test(struct device *dev) | 177 | static struct nfit_test *to_nfit_test(struct device *dev) |
174 | { | 178 | { |
175 | struct platform_device *pdev = to_platform_device(dev); | 179 | struct platform_device *pdev = to_platform_device(dev); |
@@ -234,48 +238,68 @@ static int nfit_test_cmd_set_config_data(struct nd_cmd_set_config_hdr *nd_cmd, | |||
234 | return rc; | 238 | return rc; |
235 | } | 239 | } |
236 | 240 | ||
237 | #define NFIT_TEST_ARS_RECORDS 4 | ||
238 | #define NFIT_TEST_CLEAR_ERR_UNIT 256 | 241 | #define NFIT_TEST_CLEAR_ERR_UNIT 256 |
239 | 242 | ||
240 | static int nfit_test_cmd_ars_cap(struct nd_cmd_ars_cap *nd_cmd, | 243 | static int nfit_test_cmd_ars_cap(struct nd_cmd_ars_cap *nd_cmd, |
241 | unsigned int buf_len) | 244 | unsigned int buf_len) |
242 | { | 245 | { |
246 | int ars_recs; | ||
247 | |||
243 | if (buf_len < sizeof(*nd_cmd)) | 248 | if (buf_len < sizeof(*nd_cmd)) |
244 | return -EINVAL; | 249 | return -EINVAL; |
245 | 250 | ||
251 | /* for testing, only store up to n records that fit within 4k */ | ||
252 | ars_recs = SZ_4K / sizeof(struct nd_ars_record); | ||
253 | |||
246 | nd_cmd->max_ars_out = sizeof(struct nd_cmd_ars_status) | 254 | nd_cmd->max_ars_out = sizeof(struct nd_cmd_ars_status) |
247 | + NFIT_TEST_ARS_RECORDS * sizeof(struct nd_ars_record); | 255 | + ars_recs * sizeof(struct nd_ars_record); |
248 | nd_cmd->status = (ND_ARS_PERSISTENT | ND_ARS_VOLATILE) << 16; | 256 | nd_cmd->status = (ND_ARS_PERSISTENT | ND_ARS_VOLATILE) << 16; |
249 | nd_cmd->clear_err_unit = NFIT_TEST_CLEAR_ERR_UNIT; | 257 | nd_cmd->clear_err_unit = NFIT_TEST_CLEAR_ERR_UNIT; |
250 | 258 | ||
251 | return 0; | 259 | return 0; |
252 | } | 260 | } |
253 | 261 | ||
254 | /* | 262 | static void post_ars_status(struct ars_state *ars_state, |
255 | * Initialize the ars_state to return an ars_result 1 second in the future with | 263 | struct badrange *badrange, u64 addr, u64 len) |
256 | * a 4K error range in the middle of the requested address range. | ||
257 | */ | ||
258 | static void post_ars_status(struct ars_state *ars_state, u64 addr, u64 len) | ||
259 | { | 264 | { |
260 | struct nd_cmd_ars_status *ars_status; | 265 | struct nd_cmd_ars_status *ars_status; |
261 | struct nd_ars_record *ars_record; | 266 | struct nd_ars_record *ars_record; |
267 | struct badrange_entry *be; | ||
268 | u64 end = addr + len - 1; | ||
269 | int i = 0; | ||
262 | 270 | ||
263 | ars_state->deadline = jiffies + 1*HZ; | 271 | ars_state->deadline = jiffies + 1*HZ; |
264 | ars_status = ars_state->ars_status; | 272 | ars_status = ars_state->ars_status; |
265 | ars_status->status = 0; | 273 | ars_status->status = 0; |
266 | ars_status->out_length = sizeof(struct nd_cmd_ars_status) | ||
267 | + sizeof(struct nd_ars_record); | ||
268 | ars_status->address = addr; | 274 | ars_status->address = addr; |
269 | ars_status->length = len; | 275 | ars_status->length = len; |
270 | ars_status->type = ND_ARS_PERSISTENT; | 276 | ars_status->type = ND_ARS_PERSISTENT; |
271 | ars_status->num_records = 1; | 277 | |
272 | ars_record = &ars_status->records[0]; | 278 | spin_lock(&badrange->lock); |
273 | ars_record->handle = 0; | 279 | list_for_each_entry(be, &badrange->list, list) { |
274 | ars_record->err_address = addr + len / 2; | 280 | u64 be_end = be->start + be->length - 1; |
275 | ars_record->length = SZ_4K; | 281 | u64 rstart, rend; |
282 | |||
283 | /* skip entries outside the range */ | ||
284 | if (be_end < addr || be->start > end) | ||
285 | continue; | ||
286 | |||
287 | rstart = (be->start < addr) ? addr : be->start; | ||
288 | rend = (be_end < end) ? be_end : end; | ||
289 | ars_record = &ars_status->records[i]; | ||
290 | ars_record->handle = 0; | ||
291 | ars_record->err_address = rstart; | ||
292 | ars_record->length = rend - rstart + 1; | ||
293 | i++; | ||
294 | } | ||
295 | spin_unlock(&badrange->lock); | ||
296 | ars_status->num_records = i; | ||
297 | ars_status->out_length = sizeof(struct nd_cmd_ars_status) | ||
298 | + i * sizeof(struct nd_ars_record); | ||
276 | } | 299 | } |
277 | 300 | ||
278 | static int nfit_test_cmd_ars_start(struct ars_state *ars_state, | 301 | static int nfit_test_cmd_ars_start(struct nfit_test *t, |
302 | struct ars_state *ars_state, | ||
279 | struct nd_cmd_ars_start *ars_start, unsigned int buf_len, | 303 | struct nd_cmd_ars_start *ars_start, unsigned int buf_len, |
280 | int *cmd_rc) | 304 | int *cmd_rc) |
281 | { | 305 | { |
@@ -289,7 +313,7 @@ static int nfit_test_cmd_ars_start(struct ars_state *ars_state, | |||
289 | } else { | 313 | } else { |
290 | ars_start->status = 0; | 314 | ars_start->status = 0; |
291 | ars_start->scrub_time = 1; | 315 | ars_start->scrub_time = 1; |
292 | post_ars_status(ars_state, ars_start->address, | 316 | post_ars_status(ars_state, &t->badrange, ars_start->address, |
293 | ars_start->length); | 317 | ars_start->length); |
294 | *cmd_rc = 0; | 318 | *cmd_rc = 0; |
295 | } | 319 | } |
@@ -456,6 +480,93 @@ static int nfit_test_cmd_smart_threshold(struct nd_cmd_smart_threshold *smart_t, | |||
456 | return 0; | 480 | return 0; |
457 | } | 481 | } |
458 | 482 | ||
483 | static void uc_error_notify(struct work_struct *work) | ||
484 | { | ||
485 | struct nfit_test *t = container_of(work, typeof(*t), work); | ||
486 | |||
487 | __acpi_nfit_notify(&t->pdev.dev, t, NFIT_NOTIFY_UC_MEMORY_ERROR); | ||
488 | } | ||
489 | |||
490 | static int nfit_test_cmd_ars_error_inject(struct nfit_test *t, | ||
491 | struct nd_cmd_ars_err_inj *err_inj, unsigned int buf_len) | ||
492 | { | ||
493 | int rc; | ||
494 | |||
495 | if (buf_len < sizeof(*err_inj)) { | ||
496 | rc = -EINVAL; | ||
497 | goto err; | ||
498 | } | ||
499 | |||
500 | if (err_inj->err_inj_spa_range_length <= 0) { | ||
501 | rc = -EINVAL; | ||
502 | goto err; | ||
503 | } | ||
504 | |||
505 | rc = badrange_add(&t->badrange, err_inj->err_inj_spa_range_base, | ||
506 | err_inj->err_inj_spa_range_length); | ||
507 | if (rc < 0) | ||
508 | goto err; | ||
509 | |||
510 | if (err_inj->err_inj_options & (1 << ND_ARS_ERR_INJ_OPT_NOTIFY)) | ||
511 | queue_work(nfit_wq, &t->work); | ||
512 | |||
513 | err_inj->status = 0; | ||
514 | return 0; | ||
515 | |||
516 | err: | ||
517 | err_inj->status = NFIT_ARS_INJECT_INVALID; | ||
518 | return rc; | ||
519 | } | ||
520 | |||
521 | static int nfit_test_cmd_ars_inject_clear(struct nfit_test *t, | ||
522 | struct nd_cmd_ars_err_inj_clr *err_clr, unsigned int buf_len) | ||
523 | { | ||
524 | int rc; | ||
525 | |||
526 | if (buf_len < sizeof(*err_clr)) { | ||
527 | rc = -EINVAL; | ||
528 | goto err; | ||
529 | } | ||
530 | |||
531 | if (err_clr->err_inj_clr_spa_range_length <= 0) { | ||
532 | rc = -EINVAL; | ||
533 | goto err; | ||
534 | } | ||
535 | |||
536 | badrange_forget(&t->badrange, err_clr->err_inj_clr_spa_range_base, | ||
537 | err_clr->err_inj_clr_spa_range_length); | ||
538 | |||
539 | err_clr->status = 0; | ||
540 | return 0; | ||
541 | |||
542 | err: | ||
543 | err_clr->status = NFIT_ARS_INJECT_INVALID; | ||
544 | return rc; | ||
545 | } | ||
546 | |||
547 | static int nfit_test_cmd_ars_inject_status(struct nfit_test *t, | ||
548 | struct nd_cmd_ars_err_inj_stat *err_stat, | ||
549 | unsigned int buf_len) | ||
550 | { | ||
551 | struct badrange_entry *be; | ||
552 | int max = SZ_4K / sizeof(struct nd_error_stat_query_record); | ||
553 | int i = 0; | ||
554 | |||
555 | err_stat->status = 0; | ||
556 | spin_lock(&t->badrange.lock); | ||
557 | list_for_each_entry(be, &t->badrange.list, list) { | ||
558 | err_stat->record[i].err_inj_stat_spa_range_base = be->start; | ||
559 | err_stat->record[i].err_inj_stat_spa_range_length = be->length; | ||
560 | i++; | ||
561 | if (i > max) | ||
562 | break; | ||
563 | } | ||
564 | spin_unlock(&t->badrange.lock); | ||
565 | err_stat->inj_err_rec_count = i; | ||
566 | |||
567 | return 0; | ||
568 | } | ||
569 | |||
459 | static int nfit_test_ctl(struct nvdimm_bus_descriptor *nd_desc, | 570 | static int nfit_test_ctl(struct nvdimm_bus_descriptor *nd_desc, |
460 | struct nvdimm *nvdimm, unsigned int cmd, void *buf, | 571 | struct nvdimm *nvdimm, unsigned int cmd, void *buf, |
461 | unsigned int buf_len, int *cmd_rc) | 572 | unsigned int buf_len, int *cmd_rc) |
@@ -543,6 +654,18 @@ static int nfit_test_ctl(struct nvdimm_bus_descriptor *nd_desc, | |||
543 | rc = nfit_test_cmd_translate_spa( | 654 | rc = nfit_test_cmd_translate_spa( |
544 | acpi_desc->nvdimm_bus, buf, buf_len); | 655 | acpi_desc->nvdimm_bus, buf, buf_len); |
545 | return rc; | 656 | return rc; |
657 | case NFIT_CMD_ARS_INJECT_SET: | ||
658 | rc = nfit_test_cmd_ars_error_inject(t, buf, | ||
659 | buf_len); | ||
660 | return rc; | ||
661 | case NFIT_CMD_ARS_INJECT_CLEAR: | ||
662 | rc = nfit_test_cmd_ars_inject_clear(t, buf, | ||
663 | buf_len); | ||
664 | return rc; | ||
665 | case NFIT_CMD_ARS_INJECT_GET: | ||
666 | rc = nfit_test_cmd_ars_inject_status(t, buf, | ||
667 | buf_len); | ||
668 | return rc; | ||
546 | default: | 669 | default: |
547 | return -ENOTTY; | 670 | return -ENOTTY; |
548 | } | 671 | } |
@@ -556,8 +679,8 @@ static int nfit_test_ctl(struct nvdimm_bus_descriptor *nd_desc, | |||
556 | rc = nfit_test_cmd_ars_cap(buf, buf_len); | 679 | rc = nfit_test_cmd_ars_cap(buf, buf_len); |
557 | break; | 680 | break; |
558 | case ND_CMD_ARS_START: | 681 | case ND_CMD_ARS_START: |
559 | rc = nfit_test_cmd_ars_start(ars_state, buf, buf_len, | 682 | rc = nfit_test_cmd_ars_start(t, ars_state, buf, |
560 | cmd_rc); | 683 | buf_len, cmd_rc); |
561 | break; | 684 | break; |
562 | case ND_CMD_ARS_STATUS: | 685 | case ND_CMD_ARS_STATUS: |
563 | rc = nfit_test_cmd_ars_status(ars_state, buf, buf_len, | 686 | rc = nfit_test_cmd_ars_status(ars_state, buf, buf_len, |
@@ -664,10 +787,9 @@ static struct nfit_test_resource *nfit_test_lookup(resource_size_t addr) | |||
664 | 787 | ||
665 | static int ars_state_init(struct device *dev, struct ars_state *ars_state) | 788 | static int ars_state_init(struct device *dev, struct ars_state *ars_state) |
666 | { | 789 | { |
790 | /* for testing, only store up to n records that fit within 4k */ | ||
667 | ars_state->ars_status = devm_kzalloc(dev, | 791 | ars_state->ars_status = devm_kzalloc(dev, |
668 | sizeof(struct nd_cmd_ars_status) | 792 | sizeof(struct nd_cmd_ars_status) + SZ_4K, GFP_KERNEL); |
669 | + sizeof(struct nd_ars_record) * NFIT_TEST_ARS_RECORDS, | ||
670 | GFP_KERNEL); | ||
671 | if (!ars_state->ars_status) | 793 | if (!ars_state->ars_status) |
672 | return -ENOMEM; | 794 | return -ENOMEM; |
673 | spin_lock_init(&ars_state->lock); | 795 | spin_lock_init(&ars_state->lock); |
@@ -1517,7 +1639,8 @@ static void nfit_test0_setup(struct nfit_test *t) | |||
1517 | + i * sizeof(u64); | 1639 | + i * sizeof(u64); |
1518 | } | 1640 | } |
1519 | 1641 | ||
1520 | post_ars_status(&t->ars_state, t->spa_set_dma[0], SPA0_SIZE); | 1642 | post_ars_status(&t->ars_state, &t->badrange, t->spa_set_dma[0], |
1643 | SPA0_SIZE); | ||
1521 | 1644 | ||
1522 | acpi_desc = &t->acpi_desc; | 1645 | acpi_desc = &t->acpi_desc; |
1523 | set_bit(ND_CMD_GET_CONFIG_SIZE, &acpi_desc->dimm_cmd_force_en); | 1646 | set_bit(ND_CMD_GET_CONFIG_SIZE, &acpi_desc->dimm_cmd_force_en); |
@@ -1531,6 +1654,9 @@ static void nfit_test0_setup(struct nfit_test *t) | |||
1531 | set_bit(ND_CMD_CALL, &acpi_desc->bus_cmd_force_en); | 1654 | set_bit(ND_CMD_CALL, &acpi_desc->bus_cmd_force_en); |
1532 | set_bit(ND_CMD_SMART_THRESHOLD, &acpi_desc->dimm_cmd_force_en); | 1655 | set_bit(ND_CMD_SMART_THRESHOLD, &acpi_desc->dimm_cmd_force_en); |
1533 | set_bit(NFIT_CMD_TRANSLATE_SPA, &acpi_desc->bus_nfit_cmd_force_en); | 1656 | set_bit(NFIT_CMD_TRANSLATE_SPA, &acpi_desc->bus_nfit_cmd_force_en); |
1657 | set_bit(NFIT_CMD_ARS_INJECT_SET, &acpi_desc->bus_nfit_cmd_force_en); | ||
1658 | set_bit(NFIT_CMD_ARS_INJECT_CLEAR, &acpi_desc->bus_nfit_cmd_force_en); | ||
1659 | set_bit(NFIT_CMD_ARS_INJECT_GET, &acpi_desc->bus_nfit_cmd_force_en); | ||
1534 | } | 1660 | } |
1535 | 1661 | ||
1536 | static void nfit_test1_setup(struct nfit_test *t) | 1662 | static void nfit_test1_setup(struct nfit_test *t) |
@@ -1620,7 +1746,8 @@ static void nfit_test1_setup(struct nfit_test *t) | |||
1620 | dcr->code = NFIT_FIC_BYTE; | 1746 | dcr->code = NFIT_FIC_BYTE; |
1621 | dcr->windows = 0; | 1747 | dcr->windows = 0; |
1622 | 1748 | ||
1623 | post_ars_status(&t->ars_state, t->spa_set_dma[0], SPA2_SIZE); | 1749 | post_ars_status(&t->ars_state, &t->badrange, t->spa_set_dma[0], |
1750 | SPA2_SIZE); | ||
1624 | 1751 | ||
1625 | acpi_desc = &t->acpi_desc; | 1752 | acpi_desc = &t->acpi_desc; |
1626 | set_bit(ND_CMD_ARS_CAP, &acpi_desc->bus_cmd_force_en); | 1753 | set_bit(ND_CMD_ARS_CAP, &acpi_desc->bus_cmd_force_en); |
@@ -1718,7 +1845,10 @@ static int nfit_ctl_test(struct device *dev) | |||
1718 | .module = THIS_MODULE, | 1845 | .module = THIS_MODULE, |
1719 | .provider_name = "ACPI.NFIT", | 1846 | .provider_name = "ACPI.NFIT", |
1720 | .ndctl = acpi_nfit_ctl, | 1847 | .ndctl = acpi_nfit_ctl, |
1721 | .bus_dsm_mask = 1UL << NFIT_CMD_TRANSLATE_SPA, | 1848 | .bus_dsm_mask = 1UL << NFIT_CMD_TRANSLATE_SPA |
1849 | | 1UL << NFIT_CMD_ARS_INJECT_SET | ||
1850 | | 1UL << NFIT_CMD_ARS_INJECT_CLEAR | ||
1851 | | 1UL << NFIT_CMD_ARS_INJECT_GET, | ||
1722 | }, | 1852 | }, |
1723 | .dev = &adev->dev, | 1853 | .dev = &adev->dev, |
1724 | }; | 1854 | }; |
@@ -2017,6 +2147,10 @@ static __init int nfit_test_init(void) | |||
2017 | 2147 | ||
2018 | nfit_test_setup(nfit_test_lookup, nfit_test_evaluate_dsm); | 2148 | nfit_test_setup(nfit_test_lookup, nfit_test_evaluate_dsm); |
2019 | 2149 | ||
2150 | nfit_wq = create_singlethread_workqueue("nfit"); | ||
2151 | if (!nfit_wq) | ||
2152 | return -ENOMEM; | ||
2153 | |||
2020 | nfit_test_dimm = class_create(THIS_MODULE, "nfit_test_dimm"); | 2154 | nfit_test_dimm = class_create(THIS_MODULE, "nfit_test_dimm"); |
2021 | if (IS_ERR(nfit_test_dimm)) { | 2155 | if (IS_ERR(nfit_test_dimm)) { |
2022 | rc = PTR_ERR(nfit_test_dimm); | 2156 | rc = PTR_ERR(nfit_test_dimm); |
@@ -2033,6 +2167,7 @@ static __init int nfit_test_init(void) | |||
2033 | goto err_register; | 2167 | goto err_register; |
2034 | } | 2168 | } |
2035 | INIT_LIST_HEAD(&nfit_test->resources); | 2169 | INIT_LIST_HEAD(&nfit_test->resources); |
2170 | badrange_init(&nfit_test->badrange); | ||
2036 | switch (i) { | 2171 | switch (i) { |
2037 | case 0: | 2172 | case 0: |
2038 | nfit_test->num_pm = NUM_PM; | 2173 | nfit_test->num_pm = NUM_PM; |
@@ -2068,6 +2203,7 @@ static __init int nfit_test_init(void) | |||
2068 | goto err_register; | 2203 | goto err_register; |
2069 | 2204 | ||
2070 | instances[i] = nfit_test; | 2205 | instances[i] = nfit_test; |
2206 | INIT_WORK(&nfit_test->work, uc_error_notify); | ||
2071 | } | 2207 | } |
2072 | 2208 | ||
2073 | rc = platform_driver_register(&nfit_test_driver); | 2209 | rc = platform_driver_register(&nfit_test_driver); |
@@ -2076,6 +2212,7 @@ static __init int nfit_test_init(void) | |||
2076 | return 0; | 2212 | return 0; |
2077 | 2213 | ||
2078 | err_register: | 2214 | err_register: |
2215 | destroy_workqueue(nfit_wq); | ||
2079 | for (i = 0; i < NUM_NFITS; i++) | 2216 | for (i = 0; i < NUM_NFITS; i++) |
2080 | if (instances[i]) | 2217 | if (instances[i]) |
2081 | platform_device_unregister(&instances[i]->pdev); | 2218 | platform_device_unregister(&instances[i]->pdev); |
@@ -2091,6 +2228,8 @@ static __exit void nfit_test_exit(void) | |||
2091 | { | 2228 | { |
2092 | int i; | 2229 | int i; |
2093 | 2230 | ||
2231 | flush_workqueue(nfit_wq); | ||
2232 | destroy_workqueue(nfit_wq); | ||
2094 | for (i = 0; i < NUM_NFITS; i++) | 2233 | for (i = 0; i < NUM_NFITS; i++) |
2095 | platform_device_unregister(&instances[i]->pdev); | 2234 | platform_device_unregister(&instances[i]->pdev); |
2096 | platform_driver_unregister(&nfit_test_driver); | 2235 | platform_driver_unregister(&nfit_test_driver); |
diff --git a/tools/testing/nvdimm/test/nfit_test.h b/tools/testing/nvdimm/test/nfit_test.h index 52c83be9dcfa..113b44675a71 100644 --- a/tools/testing/nvdimm/test/nfit_test.h +++ b/tools/testing/nvdimm/test/nfit_test.h | |||
@@ -33,6 +33,11 @@ struct nfit_test_resource { | |||
33 | }; | 33 | }; |
34 | 34 | ||
35 | #define ND_TRANSLATE_SPA_STATUS_INVALID_SPA 2 | 35 | #define ND_TRANSLATE_SPA_STATUS_INVALID_SPA 2 |
36 | #define NFIT_ARS_INJECT_INVALID 2 | ||
37 | |||
38 | enum err_inj_options { | ||
39 | ND_ARS_ERR_INJ_OPT_NOTIFY = 0, | ||
40 | }; | ||
36 | 41 | ||
37 | /* nfit commands */ | 42 | /* nfit commands */ |
38 | enum nfit_cmd_num { | 43 | enum nfit_cmd_num { |