1 /* 2 * salinfo.c 3 * 4 * Creates entries in /proc/sal for various system features. 5 * 6 * Copyright (c) 2003, 2006 Silicon Graphics, Inc. All rights reserved. 7 * Copyright (c) 2003 Hewlett-Packard Co 8 * Bjorn Helgaas <bjorn.helgaas@hp.com> 9 * 10 * 10/30/2001 jbarnes@sgi.com copied much of Stephane's palinfo 11 * code to create this file 12 * Oct 23 2003 kaos@sgi.com 13 * Replace IPI with set_cpus_allowed() to read a record from the required cpu. 14 * Redesign salinfo log processing to separate interrupt and user space 15 * contexts. 16 * Cache the record across multi-block reads from user space. 17 * Support > 64 cpus. 18 * Delete module_exit and MOD_INC/DEC_COUNT, salinfo cannot be a module. 19 * 20 * Jan 28 2004 kaos@sgi.com 21 * Periodically check for outstanding MCA or INIT records. 22 * 23 * Dec 5 2004 kaos@sgi.com 24 * Standardize which records are cleared automatically. 25 * 26 * Aug 18 2005 kaos@sgi.com 27 * mca.c may not pass a buffer, a NULL buffer just indicates that a new 28 * record is available in SAL. 29 * Replace some NR_CPUS by cpus_online, for hotplug cpu. 30 * 31 * Jan 5 2006 kaos@sgi.com 32 * Handle hotplug cpus coming online. 33 * Handle hotplug cpus going offline while they still have outstanding records. 34 * Use the cpu_* macros consistently. 35 * Replace the counting semaphore with a mutex and a test if the cpumask is non-empty. 36 * Modify the locking to make the test for "work to do" an atomic operation. 37 */ 38 39 #include <linux/capability.h> 40 #include <linux/cpu.h> 41 #include <linux/types.h> 42 #include <linux/proc_fs.h> 43 #include <linux/seq_file.h> 44 #include <linux/module.h> 45 #include <linux/smp.h> 46 #include <linux/timer.h> 47 #include <linux/vmalloc.h> 48 #include <linux/semaphore.h> 49 50 #include <asm/sal.h> 51 #include <asm/uaccess.h> 52 53 MODULE_AUTHOR("Jesse Barnes <jbarnes@sgi.com>"); 54 MODULE_DESCRIPTION("/proc interface to IA-64 SAL features"); 55 MODULE_LICENSE("GPL"); 56 57 static const struct file_operations proc_salinfo_fops; 58 59 typedef struct { 60 const char *name; /* name of the proc entry */ 61 unsigned long feature; /* feature bit */ 62 struct proc_dir_entry *entry; /* registered entry (removal) */ 63 } salinfo_entry_t; 64 65 /* 66 * List {name,feature} pairs for every entry in /proc/sal/<feature> 67 * that this module exports 68 */ 69 static const salinfo_entry_t salinfo_entries[]={ 70 { "bus_lock", IA64_SAL_PLATFORM_FEATURE_BUS_LOCK, }, 71 { "irq_redirection", IA64_SAL_PLATFORM_FEATURE_IRQ_REDIR_HINT, }, 72 { "ipi_redirection", IA64_SAL_PLATFORM_FEATURE_IPI_REDIR_HINT, }, 73 { "itc_drift", IA64_SAL_PLATFORM_FEATURE_ITC_DRIFT, }, 74 }; 75 76 #define NR_SALINFO_ENTRIES ARRAY_SIZE(salinfo_entries) 77 78 static char *salinfo_log_name[] = { 79 "mca", 80 "init", 81 "cmc", 82 "cpe", 83 }; 84 85 static struct proc_dir_entry *salinfo_proc_entries[ 86 ARRAY_SIZE(salinfo_entries) + /* /proc/sal/bus_lock */ 87 ARRAY_SIZE(salinfo_log_name) + /* /proc/sal/{mca,...} */ 88 (2 * ARRAY_SIZE(salinfo_log_name)) + /* /proc/sal/mca/{event,data} */ 89 1]; /* /proc/sal */ 90 91 /* Some records we get ourselves, some are accessed as saved data in buffers 92 * that are owned by mca.c. 93 */ 94 struct salinfo_data_saved { 95 u8* buffer; 96 u64 size; 97 u64 id; 98 int cpu; 99 }; 100 101 /* State transitions. Actions are :- 102 * Write "read <cpunum>" to the data file. 103 * Write "clear <cpunum>" to the data file. 104 * Write "oemdata <cpunum> <offset> to the data file. 105 * Read from the data file. 106 * Close the data file. 107 * 108 * Start state is NO_DATA. 109 * 110 * NO_DATA 111 * write "read <cpunum>" -> NO_DATA or LOG_RECORD. 112 * write "clear <cpunum>" -> NO_DATA or LOG_RECORD. 113 * write "oemdata <cpunum> <offset> -> return -EINVAL. 114 * read data -> return EOF. 115 * close -> unchanged. Free record areas. 116 * 117 * LOG_RECORD 118 * write "read <cpunum>" -> NO_DATA or LOG_RECORD. 119 * write "clear <cpunum>" -> NO_DATA or LOG_RECORD. 120 * write "oemdata <cpunum> <offset> -> format the oem data, goto OEMDATA. 121 * read data -> return the INIT/MCA/CMC/CPE record. 122 * close -> unchanged. Keep record areas. 123 * 124 * OEMDATA 125 * write "read <cpunum>" -> NO_DATA or LOG_RECORD. 126 * write "clear <cpunum>" -> NO_DATA or LOG_RECORD. 127 * write "oemdata <cpunum> <offset> -> format the oem data, goto OEMDATA. 128 * read data -> return the formatted oemdata. 129 * close -> unchanged. Keep record areas. 130 * 131 * Closing the data file does not change the state. This allows shell scripts 132 * to manipulate salinfo data, each shell redirection opens the file, does one 133 * action then closes it again. The record areas are only freed at close when 134 * the state is NO_DATA. 135 */ 136 enum salinfo_state { 137 STATE_NO_DATA, 138 STATE_LOG_RECORD, 139 STATE_OEMDATA, 140 }; 141 142 struct salinfo_data { 143 cpumask_t cpu_event; /* which cpus have outstanding events */ 144 wait_queue_head_t read_wait; 145 u8 *log_buffer; 146 u64 log_size; 147 u8 *oemdata; /* decoded oem data */ 148 u64 oemdata_size; 149 int open; /* single-open to prevent races */ 150 u8 type; 151 u8 saved_num; /* using a saved record? */ 152 enum salinfo_state state :8; /* processing state */ 153 u8 padding; 154 int cpu_check; /* next CPU to check */ 155 struct salinfo_data_saved data_saved[5];/* save last 5 records from mca.c, must be < 255 */ 156 }; 157 158 static struct salinfo_data salinfo_data[ARRAY_SIZE(salinfo_log_name)]; 159 160 static DEFINE_SPINLOCK(data_lock); 161 static DEFINE_SPINLOCK(data_saved_lock); 162 163 /** salinfo_platform_oemdata - optional callback to decode oemdata from an error 164 * record. 165 * @sect_header: pointer to the start of the section to decode. 166 * @oemdata: returns vmalloc area containing the decoded output. 167 * @oemdata_size: returns length of decoded output (strlen). 168 * 169 * Description: If user space asks for oem data to be decoded by the kernel 170 * and/or prom and the platform has set salinfo_platform_oemdata to the address 171 * of a platform specific routine then call that routine. salinfo_platform_oemdata 172 * vmalloc's and formats its output area, returning the address of the text 173 * and its strlen. Returns 0 for success, -ve for error. The callback is 174 * invoked on the cpu that generated the error record. 175 */ 176 int (*salinfo_platform_oemdata)(const u8 *sect_header, u8 **oemdata, u64 *oemdata_size); 177 178 struct salinfo_platform_oemdata_parms { 179 const u8 *efi_guid; 180 u8 **oemdata; 181 u64 *oemdata_size; 182 int ret; 183 }; 184 185 static void 186 salinfo_platform_oemdata_cpu(void *context) 187 { 188 struct salinfo_platform_oemdata_parms *parms = context; 189 parms->ret = salinfo_platform_oemdata(parms->efi_guid, parms->oemdata, parms->oemdata_size); 190 } 191 192 static void 193 shift1_data_saved (struct salinfo_data *data, int shift) 194 { 195 memcpy(data->data_saved+shift, data->data_saved+shift+1, 196 (ARRAY_SIZE(data->data_saved) - (shift+1)) * sizeof(data->data_saved[0])); 197 memset(data->data_saved + ARRAY_SIZE(data->data_saved) - 1, 0, 198 sizeof(data->data_saved[0])); 199 } 200 201 /* This routine is invoked in interrupt context. Note: mca.c enables 202 * interrupts before calling this code for CMC/CPE. MCA and INIT events are 203 * not irq safe, do not call any routines that use spinlocks, they may deadlock. 204 * MCA and INIT records are recorded, a timer event will look for any 205 * outstanding events and wake up the user space code. 206 * 207 * The buffer passed from mca.c points to the output from ia64_log_get. This is 208 * a persistent buffer but its contents can change between the interrupt and 209 * when user space processes the record. Save the record id to identify 210 * changes. If the buffer is NULL then just update the bitmap. 211 */ 212 void 213 salinfo_log_wakeup(int type, u8 *buffer, u64 size, int irqsafe) 214 { 215 struct salinfo_data *data = salinfo_data + type; 216 struct salinfo_data_saved *data_saved; 217 unsigned long flags = 0; 218 int i; 219 int saved_size = ARRAY_SIZE(data->data_saved); 220 221 BUG_ON(type >= ARRAY_SIZE(salinfo_log_name)); 222 223 if (irqsafe) 224 spin_lock_irqsave(&data_saved_lock, flags); 225 if (buffer) { 226 for (i = 0, data_saved = data->data_saved; i < saved_size; ++i, ++data_saved) { 227 if (!data_saved->buffer) 228 break; 229 } 230 if (i == saved_size) { 231 if (!data->saved_num) { 232 shift1_data_saved(data, 0); 233 data_saved = data->data_saved + saved_size - 1; 234 } else 235 data_saved = NULL; 236 } 237 if (data_saved) { 238 data_saved->cpu = smp_processor_id(); 239 data_saved->id = ((sal_log_record_header_t *)buffer)->id; 240 data_saved->size = size; 241 data_saved->buffer = buffer; 242 } 243 } 244 cpumask_set_cpu(smp_processor_id(), &data->cpu_event); 245 if (irqsafe) { 246 wake_up_interruptible(&data->read_wait); 247 spin_unlock_irqrestore(&data_saved_lock, flags); 248 } 249 } 250 251 /* Check for outstanding MCA/INIT records every minute (arbitrary) */ 252 #define SALINFO_TIMER_DELAY (60*HZ) 253 static struct timer_list salinfo_timer; 254 extern void ia64_mlogbuf_dump(void); 255 256 static void 257 salinfo_timeout_check(struct salinfo_data *data) 258 { 259 if (!data->open) 260 return; 261 if (!cpumask_empty(&data->cpu_event)) 262 wake_up_interruptible(&data->read_wait); 263 } 264 265 static void 266 salinfo_timeout (unsigned long arg) 267 { 268 ia64_mlogbuf_dump(); 269 salinfo_timeout_check(salinfo_data + SAL_INFO_TYPE_MCA); 270 salinfo_timeout_check(salinfo_data + SAL_INFO_TYPE_INIT); 271 salinfo_timer.expires = jiffies + SALINFO_TIMER_DELAY; 272 add_timer(&salinfo_timer); 273 } 274 275 static int 276 salinfo_event_open(struct inode *inode, struct file *file) 277 { 278 if (!capable(CAP_SYS_ADMIN)) 279 return -EPERM; 280 return 0; 281 } 282 283 static ssize_t 284 salinfo_event_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) 285 { 286 struct salinfo_data *data = PDE_DATA(file_inode(file)); 287 char cmd[32]; 288 size_t size; 289 int i, n, cpu = -1; 290 291 retry: 292 if (cpumask_empty(&data->cpu_event)) { 293 if (file->f_flags & O_NONBLOCK) 294 return -EAGAIN; 295 if (wait_event_interruptible(data->read_wait, 296 !cpumask_empty(&data->cpu_event))) 297 return -EINTR; 298 } 299 300 n = data->cpu_check; 301 for (i = 0; i < nr_cpu_ids; i++) { 302 if (cpumask_test_cpu(n, &data->cpu_event)) { 303 if (!cpu_online(n)) { 304 cpumask_clear_cpu(n, &data->cpu_event); 305 continue; 306 } 307 cpu = n; 308 break; 309 } 310 if (++n == nr_cpu_ids) 311 n = 0; 312 } 313 314 if (cpu == -1) 315 goto retry; 316 317 ia64_mlogbuf_dump(); 318 319 /* for next read, start checking at next CPU */ 320 data->cpu_check = cpu; 321 if (++data->cpu_check == nr_cpu_ids) 322 data->cpu_check = 0; 323 324 snprintf(cmd, sizeof(cmd), "read %d\n", cpu); 325 326 size = strlen(cmd); 327 if (size > count) 328 size = count; 329 if (copy_to_user(buffer, cmd, size)) 330 return -EFAULT; 331 332 return size; 333 } 334 335 static const struct file_operations salinfo_event_fops = { 336 .open = salinfo_event_open, 337 .read = salinfo_event_read, 338 .llseek = noop_llseek, 339 }; 340 341 static int 342 salinfo_log_open(struct inode *inode, struct file *file) 343 { 344 struct salinfo_data *data = PDE_DATA(inode); 345 346 if (!capable(CAP_SYS_ADMIN)) 347 return -EPERM; 348 349 spin_lock(&data_lock); 350 if (data->open) { 351 spin_unlock(&data_lock); 352 return -EBUSY; 353 } 354 data->open = 1; 355 spin_unlock(&data_lock); 356 357 if (data->state == STATE_NO_DATA && 358 !(data->log_buffer = vmalloc(ia64_sal_get_state_info_size(data->type)))) { 359 data->open = 0; 360 return -ENOMEM; 361 } 362 363 return 0; 364 } 365 366 static int 367 salinfo_log_release(struct inode *inode, struct file *file) 368 { 369 struct salinfo_data *data = PDE_DATA(inode); 370 371 if (data->state == STATE_NO_DATA) { 372 vfree(data->log_buffer); 373 vfree(data->oemdata); 374 data->log_buffer = NULL; 375 data->oemdata = NULL; 376 } 377 spin_lock(&data_lock); 378 data->open = 0; 379 spin_unlock(&data_lock); 380 return 0; 381 } 382 383 static void 384 call_on_cpu(int cpu, void (*fn)(void *), void *arg) 385 { 386 cpumask_t save_cpus_allowed = current->cpus_allowed; 387 set_cpus_allowed_ptr(current, cpumask_of(cpu)); 388 (*fn)(arg); 389 set_cpus_allowed_ptr(current, &save_cpus_allowed); 390 } 391 392 static void 393 salinfo_log_read_cpu(void *context) 394 { 395 struct salinfo_data *data = context; 396 sal_log_record_header_t *rh; 397 data->log_size = ia64_sal_get_state_info(data->type, (u64 *) data->log_buffer); 398 rh = (sal_log_record_header_t *)(data->log_buffer); 399 /* Clear corrected errors as they are read from SAL */ 400 if (rh->severity == sal_log_severity_corrected) 401 ia64_sal_clear_state_info(data->type); 402 } 403 404 static void 405 salinfo_log_new_read(int cpu, struct salinfo_data *data) 406 { 407 struct salinfo_data_saved *data_saved; 408 unsigned long flags; 409 int i; 410 int saved_size = ARRAY_SIZE(data->data_saved); 411 412 data->saved_num = 0; 413 spin_lock_irqsave(&data_saved_lock, flags); 414 retry: 415 for (i = 0, data_saved = data->data_saved; i < saved_size; ++i, ++data_saved) { 416 if (data_saved->buffer && data_saved->cpu == cpu) { 417 sal_log_record_header_t *rh = (sal_log_record_header_t *)(data_saved->buffer); 418 data->log_size = data_saved->size; 419 memcpy(data->log_buffer, rh, data->log_size); 420 barrier(); /* id check must not be moved */ 421 if (rh->id == data_saved->id) { 422 data->saved_num = i+1; 423 break; 424 } 425 /* saved record changed by mca.c since interrupt, discard it */ 426 shift1_data_saved(data, i); 427 goto retry; 428 } 429 } 430 spin_unlock_irqrestore(&data_saved_lock, flags); 431 432 if (!data->saved_num) 433 call_on_cpu(cpu, salinfo_log_read_cpu, data); 434 if (!data->log_size) { 435 data->state = STATE_NO_DATA; 436 cpumask_clear_cpu(cpu, &data->cpu_event); 437 } else { 438 data->state = STATE_LOG_RECORD; 439 } 440 } 441 442 static ssize_t 443 salinfo_log_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) 444 { 445 struct salinfo_data *data = PDE_DATA(file_inode(file)); 446 u8 *buf; 447 u64 bufsize; 448 449 if (data->state == STATE_LOG_RECORD) { 450 buf = data->log_buffer; 451 bufsize = data->log_size; 452 } else if (data->state == STATE_OEMDATA) { 453 buf = data->oemdata; 454 bufsize = data->oemdata_size; 455 } else { 456 buf = NULL; 457 bufsize = 0; 458 } 459 return simple_read_from_buffer(buffer, count, ppos, buf, bufsize); 460 } 461 462 static void 463 salinfo_log_clear_cpu(void *context) 464 { 465 struct salinfo_data *data = context; 466 ia64_sal_clear_state_info(data->type); 467 } 468 469 static int 470 salinfo_log_clear(struct salinfo_data *data, int cpu) 471 { 472 sal_log_record_header_t *rh; 473 unsigned long flags; 474 spin_lock_irqsave(&data_saved_lock, flags); 475 data->state = STATE_NO_DATA; 476 if (!cpumask_test_cpu(cpu, &data->cpu_event)) { 477 spin_unlock_irqrestore(&data_saved_lock, flags); 478 return 0; 479 } 480 cpumask_clear_cpu(cpu, &data->cpu_event); 481 if (data->saved_num) { 482 shift1_data_saved(data, data->saved_num - 1); 483 data->saved_num = 0; 484 } 485 spin_unlock_irqrestore(&data_saved_lock, flags); 486 rh = (sal_log_record_header_t *)(data->log_buffer); 487 /* Corrected errors have already been cleared from SAL */ 488 if (rh->severity != sal_log_severity_corrected) 489 call_on_cpu(cpu, salinfo_log_clear_cpu, data); 490 /* clearing a record may make a new record visible */ 491 salinfo_log_new_read(cpu, data); 492 if (data->state == STATE_LOG_RECORD) { 493 spin_lock_irqsave(&data_saved_lock, flags); 494 cpumask_set_cpu(cpu, &data->cpu_event); 495 wake_up_interruptible(&data->read_wait); 496 spin_unlock_irqrestore(&data_saved_lock, flags); 497 } 498 return 0; 499 } 500 501 static ssize_t 502 salinfo_log_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) 503 { 504 struct salinfo_data *data = PDE_DATA(file_inode(file)); 505 char cmd[32]; 506 size_t size; 507 u32 offset; 508 int cpu; 509 510 size = sizeof(cmd); 511 if (count < size) 512 size = count; 513 if (copy_from_user(cmd, buffer, size)) 514 return -EFAULT; 515 516 if (sscanf(cmd, "read %d", &cpu) == 1) { 517 salinfo_log_new_read(cpu, data); 518 } else if (sscanf(cmd, "clear %d", &cpu) == 1) { 519 int ret; 520 if ((ret = salinfo_log_clear(data, cpu))) 521 count = ret; 522 } else if (sscanf(cmd, "oemdata %d %d", &cpu, &offset) == 2) { 523 if (data->state != STATE_LOG_RECORD && data->state != STATE_OEMDATA) 524 return -EINVAL; 525 if (offset > data->log_size - sizeof(efi_guid_t)) 526 return -EINVAL; 527 data->state = STATE_OEMDATA; 528 if (salinfo_platform_oemdata) { 529 struct salinfo_platform_oemdata_parms parms = { 530 .efi_guid = data->log_buffer + offset, 531 .oemdata = &data->oemdata, 532 .oemdata_size = &data->oemdata_size 533 }; 534 call_on_cpu(cpu, salinfo_platform_oemdata_cpu, &parms); 535 if (parms.ret) 536 count = parms.ret; 537 } else 538 data->oemdata_size = 0; 539 } else 540 return -EINVAL; 541 542 return count; 543 } 544 545 static const struct file_operations salinfo_data_fops = { 546 .open = salinfo_log_open, 547 .release = salinfo_log_release, 548 .read = salinfo_log_read, 549 .write = salinfo_log_write, 550 .llseek = default_llseek, 551 }; 552 553 static int 554 salinfo_cpu_callback(struct notifier_block *nb, unsigned long action, void *hcpu) 555 { 556 unsigned int i, cpu = (unsigned long)hcpu; 557 unsigned long flags; 558 struct salinfo_data *data; 559 switch (action) { 560 case CPU_ONLINE: 561 case CPU_ONLINE_FROZEN: 562 spin_lock_irqsave(&data_saved_lock, flags); 563 for (i = 0, data = salinfo_data; 564 i < ARRAY_SIZE(salinfo_data); 565 ++i, ++data) { 566 cpumask_set_cpu(cpu, &data->cpu_event); 567 wake_up_interruptible(&data->read_wait); 568 } 569 spin_unlock_irqrestore(&data_saved_lock, flags); 570 break; 571 case CPU_DEAD: 572 case CPU_DEAD_FROZEN: 573 spin_lock_irqsave(&data_saved_lock, flags); 574 for (i = 0, data = salinfo_data; 575 i < ARRAY_SIZE(salinfo_data); 576 ++i, ++data) { 577 struct salinfo_data_saved *data_saved; 578 int j; 579 for (j = ARRAY_SIZE(data->data_saved) - 1, data_saved = data->data_saved + j; 580 j >= 0; 581 --j, --data_saved) { 582 if (data_saved->buffer && data_saved->cpu == cpu) { 583 shift1_data_saved(data, j); 584 } 585 } 586 cpumask_clear_cpu(cpu, &data->cpu_event); 587 } 588 spin_unlock_irqrestore(&data_saved_lock, flags); 589 break; 590 } 591 return NOTIFY_OK; 592 } 593 594 static struct notifier_block salinfo_cpu_notifier = 595 { 596 .notifier_call = salinfo_cpu_callback, 597 .priority = 0, 598 }; 599 600 static int __init 601 salinfo_init(void) 602 { 603 struct proc_dir_entry *salinfo_dir; /* /proc/sal dir entry */ 604 struct proc_dir_entry **sdir = salinfo_proc_entries; /* keeps track of every entry */ 605 struct proc_dir_entry *dir, *entry; 606 struct salinfo_data *data; 607 int i, j; 608 609 salinfo_dir = proc_mkdir("sal", NULL); 610 if (!salinfo_dir) 611 return 0; 612 613 for (i=0; i < NR_SALINFO_ENTRIES; i++) { 614 /* pass the feature bit in question as misc data */ 615 *sdir++ = proc_create_data(salinfo_entries[i].name, 0, salinfo_dir, 616 &proc_salinfo_fops, 617 (void *)salinfo_entries[i].feature); 618 } 619 620 cpu_notifier_register_begin(); 621 622 for (i = 0; i < ARRAY_SIZE(salinfo_log_name); i++) { 623 data = salinfo_data + i; 624 data->type = i; 625 init_waitqueue_head(&data->read_wait); 626 dir = proc_mkdir(salinfo_log_name[i], salinfo_dir); 627 if (!dir) 628 continue; 629 630 entry = proc_create_data("event", S_IRUSR, dir, 631 &salinfo_event_fops, data); 632 if (!entry) 633 continue; 634 *sdir++ = entry; 635 636 entry = proc_create_data("data", S_IRUSR | S_IWUSR, dir, 637 &salinfo_data_fops, data); 638 if (!entry) 639 continue; 640 *sdir++ = entry; 641 642 /* we missed any events before now */ 643 for_each_online_cpu(j) 644 cpumask_set_cpu(j, &data->cpu_event); 645 646 *sdir++ = dir; 647 } 648 649 *sdir++ = salinfo_dir; 650 651 init_timer(&salinfo_timer); 652 salinfo_timer.expires = jiffies + SALINFO_TIMER_DELAY; 653 salinfo_timer.function = &salinfo_timeout; 654 add_timer(&salinfo_timer); 655 656 __register_hotcpu_notifier(&salinfo_cpu_notifier); 657 658 cpu_notifier_register_done(); 659 660 return 0; 661 } 662 663 /* 664 * 'data' contains an integer that corresponds to the feature we're 665 * testing 666 */ 667 static int proc_salinfo_show(struct seq_file *m, void *v) 668 { 669 unsigned long data = (unsigned long)v; 670 seq_puts(m, (sal_platform_features & data) ? "1\n" : "0\n"); 671 return 0; 672 } 673 674 static int proc_salinfo_open(struct inode *inode, struct file *file) 675 { 676 return single_open(file, proc_salinfo_show, PDE_DATA(inode)); 677 } 678 679 static const struct file_operations proc_salinfo_fops = { 680 .open = proc_salinfo_open, 681 .read = seq_read, 682 .llseek = seq_lseek, 683 .release = single_release, 684 }; 685 686 module_init(salinfo_init); 687