driver-titan.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. /*
  2. * Copyright 2014 Vitalii Demianets
  3. * Copyright 2014 KnCMiner
  4. *
  5. * This program is free software; you can redistribute it and/or modify it
  6. * under the terms of the GNU General Public License as published by the Free
  7. * Software Foundation; either version 3 of the License, or (at your option)
  8. * any later version. See COPYING for more details.
  9. */
  10. #include <fcntl.h>
  11. #include <sys/ioctl.h>
  12. #include <linux/spi/spidev.h>
  13. #include "deviceapi.h"
  14. #include "logging.h"
  15. #include "lowl-spi.h"
  16. #include "miner.h"
  17. #include "util.h"
  18. #include "titan-asic.h"
  19. #define KNC_TITAN_DEFAULT_FREQUENCY 600
  20. #define KNC_TITAN_HWERR_DISABLE_SECS 10
  21. #define KNC_POLL_INTERVAL_US 10000
  22. #define KNC_TITAN_SPI_SPEED 3000000
  23. #define KNC_TITAN_SPI_DELAY 0
  24. #define KNC_TITAN_SPI_MODE (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH)
  25. #define KNC_TITAN_SPI_BITS 8
  26. /* Specify here minimum number of leading zeroes in hash */
  27. #define DEFAULT_DIFF_FILTERING_ZEROES 12
  28. #define DEFAULT_DIFF_FILTERING_FLOAT (1. / ((double)(0x00000000FFFFFFFF >> DEFAULT_DIFF_FILTERING_ZEROES)))
  29. #define DEFAULT_DIFF_HASHES_PER_NONCE (1 << DEFAULT_DIFF_FILTERING_ZEROES)
  30. BFG_REGISTER_DRIVER(knc_titan_drv)
  31. static const struct bfg_set_device_definition knc_titan_set_device_funcs[];
  32. struct knc_titan_core {
  33. int asicno;
  34. int dieno; /* inside asic */
  35. int coreno; /* inside die */
  36. struct knc_titan_die *die;
  37. struct cgpu_info *proc;
  38. int next_slot;
  39. int hwerr_in_row;
  40. int hwerr_disable_time;
  41. struct timeval enable_at;
  42. struct timeval first_hwerr;
  43. struct nonce_report last_nonce;
  44. };
  45. struct knc_titan_die {
  46. int asicno;
  47. int dieno; /* inside asic */
  48. int cores;
  49. struct cgpu_info *first_proc;
  50. int freq;
  51. };
  52. struct knc_titan_info {
  53. struct spi_port *spi;
  54. struct cgpu_info *cgpu;
  55. int cores;
  56. struct knc_titan_die dies[KNC_TITAN_MAX_ASICS][KNC_TITAN_DIES_PER_ASIC];
  57. bool need_flush;
  58. struct work *workqueue;
  59. int workqueue_size;
  60. int workqueue_max;
  61. int next_id;
  62. struct work *devicework;
  63. };
  64. static bool knc_titan_spi_open(const char *repr, struct spi_port * const spi)
  65. {
  66. const char * const spipath = "/dev/spidev0.1";
  67. const int fd = open(spipath, O_RDWR);
  68. const uint8_t lsbfirst = 0;
  69. if (0 > fd)
  70. return false;
  71. if (ioctl(fd, SPI_IOC_WR_MODE , &spi->mode )) goto fail;
  72. if (ioctl(fd, SPI_IOC_WR_LSB_FIRST , &lsbfirst )) goto fail;
  73. if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &spi->bits )) goto fail;
  74. if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ , &spi->speed)) goto fail;
  75. spi->fd = fd;
  76. return true;
  77. fail:
  78. close(fd);
  79. spi->fd = -1;
  80. applog(LOG_WARNING, "%s: Failed to open %s", repr, spipath);
  81. return false;
  82. }
  83. #define knc_titan_spi_txrx linux_spi_txrx
  84. static bool knc_titan_detect_one(const char *devpath)
  85. {
  86. static struct cgpu_info *prev_cgpu = NULL;
  87. struct cgpu_info *cgpu;
  88. struct spi_port *spi;
  89. struct knc_titan_info *knc;
  90. int cores = 0, asic, die;
  91. struct titan_info_response resp;
  92. char repr[6];
  93. cgpu = malloc(sizeof(*cgpu));
  94. if (unlikely(!cgpu))
  95. quit(1, "Failed to alloc cgpu_info");
  96. if (!prev_cgpu) {
  97. spi = calloc(1, sizeof(*spi));
  98. if (unlikely(!spi))
  99. quit(1, "Failed to alloc spi_port");
  100. /* Be careful, read lowl-spi.h comments for warnings */
  101. memset(spi, 0, sizeof(*spi));
  102. spi->txrx = knc_titan_spi_txrx;
  103. spi->cgpu = cgpu;
  104. spi->repr = knc_titan_drv.dname;
  105. spi->logprio = LOG_ERR;
  106. spi->speed = KNC_TITAN_SPI_SPEED;
  107. spi->delay = KNC_TITAN_SPI_DELAY;
  108. spi->mode = KNC_TITAN_SPI_MODE;
  109. spi->bits = KNC_TITAN_SPI_BITS;
  110. if (!knc_titan_spi_open(knc_titan_drv.name, spi)) {
  111. free(cgpu);
  112. return false;
  113. }
  114. knc = calloc(1, sizeof(*knc));
  115. if (unlikely(!knc))
  116. quit(1, "Failed to alloc knc_titan_info");
  117. knc->spi = spi;
  118. knc->cgpu = cgpu;
  119. knc->workqueue_max = KNC_TITAN_WORKSLOTS_PER_CORE + 1;
  120. } else {
  121. knc = prev_cgpu->device_data;
  122. spi = knc->spi;
  123. }
  124. snprintf(repr, sizeof(repr), "%s %s", knc_titan_drv.name, devpath);
  125. asic = atoi(devpath);
  126. for (die = 0; die < KNC_TITAN_DIES_PER_ASIC; ++die) {
  127. if (!knc_titan_spi_get_info(repr, spi, &resp, die, KNC_TITAN_CORES_PER_DIE))
  128. continue;
  129. if (0 < resp.cores) {
  130. knc->dies[asic][die] = (struct knc_titan_die) {
  131. .asicno = asic,
  132. .dieno = die,
  133. .cores = resp.cores,
  134. .first_proc = cgpu,
  135. .freq = KNC_TITAN_DEFAULT_FREQUENCY,
  136. };
  137. cores += resp.cores;
  138. } else {
  139. knc->dies[asic][die] = (struct knc_titan_die) {
  140. .asicno = -INT_MAX,
  141. .dieno = -INT_MAX,
  142. .cores = 0,
  143. .first_proc = NULL,
  144. };
  145. }
  146. }
  147. if (0 == cores) {
  148. free(cgpu);
  149. if (!prev_cgpu) {
  150. free(knc);
  151. close(spi->fd);
  152. free(spi);
  153. }
  154. return false;
  155. }
  156. applog(LOG_NOTICE, "%s: Found ASIC with %d cores", repr, cores);
  157. *cgpu = (struct cgpu_info) {
  158. .drv = &knc_titan_drv,
  159. .device_path = strdup(devpath),
  160. .set_device_funcs = knc_titan_set_device_funcs,
  161. .deven = DEV_ENABLED,
  162. .procs = cores,
  163. .threads = prev_cgpu ? 0 : 1,
  164. .device_data = knc,
  165. };
  166. const bool rv = add_cgpu_slave(cgpu, prev_cgpu);
  167. prev_cgpu = cgpu;
  168. return rv;
  169. }
  170. static int knc_titan_detect_auto(void)
  171. {
  172. const int first = 0, last = KNC_TITAN_MAX_ASICS - 1;
  173. char devpath[256];
  174. int found = 0, i;
  175. for (i = first; i <= last; ++i) {
  176. sprintf(devpath, "%d", i);
  177. if (knc_titan_detect_one(devpath))
  178. ++found;
  179. }
  180. return found;
  181. }
  182. static void knc_titan_detect(void)
  183. {
  184. generic_detect(&knc_titan_drv, knc_titan_detect_one, knc_titan_detect_auto, GDF_REQUIRE_DNAME | GDF_DEFAULT_NOAUTO);
  185. }
  186. static void knc_titan_clean_flush(const char *repr, struct spi_port * const spi, int die)
  187. {
  188. struct titan_report report;
  189. bool unused;
  190. knc_titan_set_work(repr, spi, &report, die, 0xFFFF, 0, NULL, true, &unused);
  191. }
  192. static bool knc_titan_init(struct thr_info * const thr)
  193. {
  194. const int max_cores = KNC_TITAN_CORES_PER_ASIC;
  195. struct thr_info *mythr;
  196. struct cgpu_info * const cgpu = thr->cgpu, *proc;
  197. struct knc_titan_core *knccore;
  198. struct knc_titan_info *knc;
  199. int i, asic, die, core_base;
  200. int total_cores = 0;
  201. for (proc = cgpu; proc; ) {
  202. proc->min_nonce_diff = DEFAULT_DIFF_FILTERING_FLOAT;
  203. if (proc->device != proc) {
  204. applog(LOG_WARNING, "%"PRIpreprv": Extra processor?", proc->proc_repr);
  205. proc = proc->next_proc;
  206. continue;
  207. }
  208. asic = atoi(proc->device_path);
  209. knc = proc->device_data;
  210. die = 0;
  211. core_base = 0;
  212. for (i = 0; i < max_cores; ++i) {
  213. while (i >= (core_base + knc->dies[asic][die].cores)) {
  214. core_base += knc->dies[asic][die].cores;
  215. if (++die >= KNC_TITAN_DIES_PER_ASIC)
  216. break;
  217. }
  218. if (die >= KNC_TITAN_DIES_PER_ASIC)
  219. break;
  220. mythr = proc->thr[0];
  221. mythr->cgpu_data = knccore = malloc(sizeof(*knccore));
  222. if (unlikely(!knccore))
  223. quit(1, "Failed to alloc knc_titan_core");
  224. *knccore = (struct knc_titan_core) {
  225. .asicno = asic,
  226. .dieno = die,
  227. .coreno = i - core_base,
  228. .next_slot = 1,
  229. .die = &(knc->dies[asic][die]),
  230. .proc = proc,
  231. .hwerr_in_row = 0,
  232. .hwerr_disable_time = KNC_TITAN_HWERR_DISABLE_SECS,
  233. };
  234. timer_set_now(&knccore->enable_at);
  235. proc->device_data = knc;
  236. ++total_cores;
  237. applog(LOG_DEBUG, "%s Allocated core %d:%d:%d", proc->device->dev_repr, asic, die, (i - core_base));
  238. if (0 == knccore->coreno)
  239. knc_titan_clean_flush(proc->device->dev_repr, knc->spi, knccore->dieno);
  240. proc = proc->next_proc;
  241. if ((!proc) || proc->device == proc)
  242. break;
  243. }
  244. knc->cores = total_cores;
  245. }
  246. cgpu_set_defaults(cgpu);
  247. if (0 >= total_cores)
  248. return false;
  249. /* Init nonce ranges for cores */
  250. double nonce_step = 4294967296.0 / total_cores;
  251. double nonce_f = 0.0;
  252. struct titan_setup_core_params setup_params = {
  253. .bad_address_mask = {0, 0},
  254. .bad_address_match = {0x3FF, 0x3FF},
  255. .difficulty = DEFAULT_DIFF_FILTERING_ZEROES,
  256. .thread_enable = 0xFF,
  257. .thread_base_address = {0, 1, 2, 3, 4, 5, 6, 7},
  258. .lookup_gap_mask = {0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7},
  259. .N_mask = {0, 0, 0, 0, 0, 0, 0, 0},
  260. .N_shift = {0, 0, 0, 0, 0, 0, 0, 0},
  261. .nonce_bottom = 0,
  262. .nonce_top = 0xFFFFFFFF,
  263. };
  264. for (proc = cgpu; proc; proc = proc->next_proc) {
  265. nonce_f += nonce_step;
  266. setup_params.nonce_bottom = setup_params.nonce_top + 1;
  267. if (NULL != proc->next_proc)
  268. setup_params.nonce_top = nonce_f;
  269. else
  270. setup_params.nonce_top = 0xFFFFFFFF;
  271. knc = proc->device_data;
  272. mythr = proc->thr[0];
  273. knccore = mythr->cgpu_data;
  274. applog(LOG_DEBUG, "%s Setup core %d:%d:%d, nonces 0x%08X - 0x%08X", proc->device->dev_repr, knccore->asicno, knccore->dieno, knccore->coreno, setup_params.nonce_bottom, setup_params.nonce_top);
  275. knc_titan_setup_core(proc->device->dev_repr, knc->spi, &setup_params, knccore->dieno, knccore->coreno);
  276. }
  277. timer_set_now(&thr->tv_poll);
  278. return true;
  279. }
  280. static bool knc_titan_prepare_work(struct thr_info *thr, struct work *work)
  281. {
  282. struct cgpu_info * const cgpu = thr->cgpu;
  283. work->nonce_diff = cgpu->min_nonce_diff;
  284. return true;
  285. }
  286. static void knc_titan_clear_last_nonce(struct knc_titan_info * const knc)
  287. {
  288. struct thr_info * mythr;
  289. struct cgpu_info *proc;
  290. struct knc_titan_core *knccore;
  291. for (proc = knc->cgpu; proc; proc = proc->next_proc) {
  292. mythr = proc->thr[0];
  293. knccore = mythr->cgpu_data;
  294. knccore->last_nonce.slot = 0;
  295. knccore->last_nonce.nonce = 0;
  296. }
  297. }
  298. static void knc_titan_set_queue_full(struct knc_titan_info * const knc)
  299. {
  300. const bool full = (knc->workqueue_size >= knc->workqueue_max);
  301. struct cgpu_info *proc;
  302. for (proc = knc->cgpu; proc; proc = proc->next_proc) {
  303. struct thr_info * const thr = proc->thr[0];
  304. thr->queue_full = full;
  305. }
  306. }
  307. static void knc_titan_remove_local_queue(struct knc_titan_info * const knc, struct work * const work)
  308. {
  309. DL_DELETE(knc->workqueue, work);
  310. free_work(work);
  311. --knc->workqueue_size;
  312. }
  313. static void knc_titan_prune_local_queue(struct thr_info *thr)
  314. {
  315. struct cgpu_info * const cgpu = thr->cgpu;
  316. struct knc_titan_info * const knc = cgpu->device_data;
  317. struct work *work, *tmp;
  318. DL_FOREACH_SAFE(knc->workqueue, work, tmp) {
  319. if (stale_work(work, false))
  320. knc_titan_remove_local_queue(knc, work);
  321. }
  322. knc_titan_set_queue_full(knc);
  323. }
  324. static bool knc_titan_queue_append(struct thr_info * const thr, struct work * const work)
  325. {
  326. struct cgpu_info * const cgpu = thr->cgpu;
  327. struct knc_titan_info * const knc = cgpu->device_data;
  328. if (knc->workqueue_size >= knc->workqueue_max) {
  329. knc_titan_prune_local_queue(thr);
  330. if (thr->queue_full)
  331. return false;
  332. }
  333. DL_APPEND(knc->workqueue, work);
  334. ++knc->workqueue_size;
  335. knc_titan_set_queue_full(knc);
  336. if (thr->queue_full)
  337. knc_titan_prune_local_queue(thr);
  338. return true;
  339. }
  340. #define HASH_LAST_ADDED(head, out) \
  341. (out = (head) ? (ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail)) : NULL)
  342. static void knc_titan_queue_flush(struct thr_info * const thr)
  343. {
  344. struct cgpu_info * const cgpu = thr->cgpu;
  345. struct knc_titan_info * const knc = cgpu->device_data;
  346. struct work *work, *tmp;
  347. if (knc->cgpu != cgpu)
  348. return;
  349. DL_FOREACH_SAFE(knc->workqueue, work, tmp){
  350. knc_titan_remove_local_queue(knc, work);
  351. }
  352. knc_titan_set_queue_full(knc);
  353. HASH_LAST_ADDED(knc->devicework, work);
  354. if (work && stale_work(work, true)) {
  355. knc->need_flush = true;
  356. timer_set_now(&thr->tv_poll);
  357. }
  358. }
  359. static void knc_titan_poll(struct thr_info * const thr)
  360. {
  361. struct thr_info *mythr;
  362. struct cgpu_info * const cgpu = thr->cgpu, *proc;
  363. struct knc_titan_info * const knc = cgpu->device_data;
  364. struct knc_titan_core *knccore;
  365. struct work *work, *tmp;
  366. int workaccept = 0;
  367. unsigned long delay_usecs = KNC_POLL_INTERVAL_US;
  368. struct titan_report report;
  369. struct titan_info_response info_resp;
  370. int asic = 0; /* TODO: the asic number must iterate from 0 to 5 */
  371. int die = 0; /* TODO: the die number must iterate from 0 to 3 */
  372. int i, tmp_int;
  373. knc_titan_prune_local_queue(thr);
  374. if (knc->need_flush)
  375. knc_titan_clear_last_nonce(knc);
  376. knccore = cgpu->thr[0]->cgpu_data;
  377. DL_FOREACH_SAFE(knc->workqueue, work, tmp) {
  378. bool work_accepted;
  379. if (!knc_titan_set_work(cgpu->dev_repr, knc->spi, &report, die, 0xFFFF, knccore->next_slot, work, knc->need_flush, &work_accepted))
  380. work_accepted = false;
  381. if (!work_accepted)
  382. break;
  383. if (knc->need_flush) {
  384. struct work *work1, *tmp1;
  385. knc->need_flush = false;
  386. applog(LOG_NOTICE, "%s: Flushing stale works", knc_titan_drv.dname);
  387. HASH_ITER(hh, knc->devicework, work1, tmp1) {
  388. HASH_DEL(knc->devicework, work1);
  389. free_work(work1);
  390. }
  391. delay_usecs = 0;
  392. }
  393. --knc->workqueue_size;
  394. DL_DELETE(knc->workqueue, work);
  395. work->device_id = knccore->next_slot;
  396. HASH_ADD(hh, knc->devicework, device_id, sizeof(work->device_id), work);
  397. if (++(knccore->next_slot) >= 16)
  398. knccore->next_slot = 1;
  399. ++workaccept;
  400. }
  401. applog(LOG_DEBUG, "%s: %d jobs accepted to queue (max=%d)", knc_titan_drv.dname, workaccept, knc->workqueue_max);
  402. while (true) {
  403. if (0 >= knc->dies[asic][die].cores)
  404. break;
  405. if (!knc_titan_spi_get_info(cgpu->dev_repr, knc->spi, &info_resp, die, knc->dies[asic][die].cores))
  406. break;
  407. for (proc = knc->dies[asic][die].first_proc; proc; proc = proc->next_proc) {
  408. mythr = proc->thr[0];
  409. knccore = mythr->cgpu_data;
  410. if ((knccore->dieno != die) || (knccore->asicno != asic))
  411. break;
  412. if (!info_resp.have_report[knccore->coreno])
  413. continue;
  414. if (!knc_titan_get_report(proc->proc_repr, knc->spi, &report, die, knccore->coreno))
  415. continue;
  416. /* if last_nonce.slot == 0, then there was a flush and all reports are stale */
  417. if (0 != knccore->last_nonce.slot) {
  418. for (i = 0; i < KNC_TITAN_NONCES_PER_REPORT; ++i) {
  419. if ((report.nonces[i].slot == knccore->last_nonce.slot) &&
  420. (report.nonces[i].nonce == knccore->last_nonce.nonce))
  421. break;
  422. tmp_int = report.nonces[i].slot;
  423. HASH_FIND_INT(knc->devicework, &tmp_int, work);
  424. if (!work) {
  425. applog(LOG_WARNING, "%"PRIpreprv": Got nonce for unknown work in slot %u", proc->proc_repr, tmp_int);
  426. continue;
  427. }
  428. if (submit_nonce(mythr, work, report.nonces[i].nonce)) {
  429. hashes_done2(mythr, DEFAULT_DIFF_HASHES_PER_NONCE, NULL);
  430. knccore->hwerr_in_row = 0;
  431. }
  432. }
  433. }
  434. knccore->last_nonce.slot = report.nonces[0].slot;
  435. knccore->last_nonce.nonce = report.nonces[0].nonce;
  436. }
  437. /* TODO: switch to next asic/die */
  438. break;
  439. }
  440. if (workaccept) {
  441. if (workaccept >= knc->workqueue_max) {
  442. knc->workqueue_max = workaccept;
  443. delay_usecs = 0;
  444. }
  445. knc_titan_set_queue_full(knc);
  446. }
  447. timer_set_delay_from_now(&thr->tv_poll, delay_usecs);
  448. }
  449. /*
  450. * specify settings / options via RPC or command line
  451. */
  452. /* support for --set-device
  453. * must be set before probing the device
  454. */
  455. static void knc_titan_set_clock_freq(struct cgpu_info * const device, int const val)
  456. {
  457. }
  458. static const char *knc_titan_set_clock(struct cgpu_info * const device, const char * const option, const char * const setting, char * const replybuf, enum bfg_set_device_replytype * const success)
  459. {
  460. knc_titan_set_clock_freq(device, atoi(setting));
  461. return NULL;
  462. }
  463. static const struct bfg_set_device_definition knc_titan_set_device_funcs[] = {
  464. { "clock", knc_titan_set_clock, NULL },
  465. { NULL },
  466. };
  467. /*
  468. * specify settings / options via TUI
  469. */
  470. #ifdef HAVE_CURSES
  471. static void knc_titan_tui_wlogprint_choices(struct cgpu_info * const proc)
  472. {
  473. wlogprint("[C]lock speed ");
  474. }
  475. static const char *knc_titan_tui_handle_choice(struct cgpu_info * const proc, const int input)
  476. {
  477. static char buf[0x100]; /* Static for replies */
  478. switch (input)
  479. {
  480. case 'c': case 'C':
  481. {
  482. sprintf(buf, "Set clock speed");
  483. char * const setting = curses_input(buf);
  484. knc_titan_set_clock_freq(proc->device, atoi(setting));
  485. return "Clock speed changed\n";
  486. }
  487. }
  488. return NULL;
  489. }
  490. static void knc_titan_wlogprint_status(struct cgpu_info * const proc)
  491. {
  492. wlogprint("Clock speed: N/A\n");
  493. }
  494. #endif
  495. struct device_drv knc_titan_drv =
  496. {
  497. /* metadata */
  498. .dname = "titan",
  499. .name = "KNC",
  500. .supported_algos = POW_SCRYPT,
  501. .drv_detect = knc_titan_detect,
  502. .thread_init = knc_titan_init,
  503. /* specify mining type - queue */
  504. .minerloop = minerloop_queue,
  505. .queue_append = knc_titan_queue_append,
  506. .queue_flush = knc_titan_queue_flush,
  507. .poll = knc_titan_poll,
  508. .prepare_work = knc_titan_prepare_work,
  509. /* TUI support - e.g. setting clock via UI */
  510. #ifdef HAVE_CURSES
  511. .proc_wlogprint_status = knc_titan_wlogprint_status,
  512. .proc_tui_wlogprint_choices = knc_titan_tui_wlogprint_choices,
  513. .proc_tui_handle_choice = knc_titan_tui_handle_choice,
  514. #endif
  515. };