/* * salinfo.c * * Creates entries in /proc/sal for various system features. * * Copyright (c) 2001 Silicon Graphics, Inc. All rights reserved. * Copyright (c) 2003 Hewlett-Packard Co * Bjorn Helgaas * * 10/30/2001 jbarnes@sgi.com copied much of Stephane's palinfo * code to create this file */ #include #include #include #include #include #include MODULE_AUTHOR("Jesse Barnes "); MODULE_DESCRIPTION("/proc interface to IA-64 SAL features"); MODULE_LICENSE("GPL"); static int salinfo_read(char *page, char **start, off_t off, int count, int *eof, void *data); typedef struct { const char *name; /* name of the proc entry */ unsigned long feature; /* feature bit */ struct proc_dir_entry *entry; /* registered entry (removal) */ } salinfo_entry_t; /* * List {name,feature} pairs for every entry in /proc/sal/ * that this module exports */ static salinfo_entry_t salinfo_entries[]={ { "bus_lock", IA64_SAL_PLATFORM_FEATURE_BUS_LOCK, }, { "irq_redirection", IA64_SAL_PLATFORM_FEATURE_IRQ_REDIR_HINT, }, { "ipi_redirection", IA64_SAL_PLATFORM_FEATURE_IPI_REDIR_HINT, }, { "itc_drift", IA64_SAL_PLATFORM_FEATURE_ITC_DRIFT, }, }; #define NR_SALINFO_ENTRIES (sizeof(salinfo_entries)/sizeof(salinfo_entry_t)) static char *salinfo_log_name[] = { "mca", "init", "cmc", "cpe", }; static struct proc_dir_entry *salinfo_proc_entries[ ARRAY_SIZE(salinfo_entries) + /* /proc/sal/bus_lock */ ARRAY_SIZE(salinfo_log_name) + /* /proc/sal/{mca,...} */ (2 * ARRAY_SIZE(salinfo_log_name)) + /* /proc/sal/mca/{event,data} */ 1]; /* /proc/sal */ struct salinfo_log_data { int type; u8 *log_buffer; u64 log_size; }; struct salinfo_event { int type; int cpu; /* next CPU to check */ volatile unsigned long cpu_mask; wait_queue_head_t queue; }; static struct salinfo_event *salinfo_event[ARRAY_SIZE(salinfo_log_name)]; struct salinfo_data { int open; /* single-open to prevent races */ int type; int cpu; /* "current" cpu for reads */ }; static struct salinfo_data salinfo_data[ARRAY_SIZE(salinfo_log_name)]; static spinlock_t data_lock; void salinfo_log_wakeup(int type) { if (type < ARRAY_SIZE(salinfo_log_name)) { struct salinfo_event *event = salinfo_event[type]; if (event) { set_bit(smp_processor_id(), &event->cpu_mask); wake_up_interruptible(&event->queue); } } } static int salinfo_event_open(struct inode *inode, struct file *file) { struct proc_dir_entry *entry = (struct proc_dir_entry *) inode->u.generic_ip; struct salinfo_event *event = entry->data; if (!suser()) return -EPERM; return 0; } static ssize_t salinfo_event_read(struct file *file, char *buffer, size_t count, loff_t *ppos) { struct inode *inode = file->f_dentry->d_inode; struct proc_dir_entry *entry = (struct proc_dir_entry *) inode->u.generic_ip; struct salinfo_event *event = entry->data; char cmd[32]; size_t size; int i, n, cpu = -1; retry: if (!event->cpu_mask) { if (file->f_flags & O_NONBLOCK) return -EAGAIN; interruptible_sleep_on(&event->queue); if (signal_pending(current)) return -EINTR; } n = event->cpu; for (i = 0; i < NR_CPUS; i++) { if (event->cpu_mask & 1UL << n) { cpu = n; break; } if (++n == NR_CPUS) n = 0; } if (cpu == -1) goto retry; /* for next read, start checking at next CPU */ event->cpu = cpu; if (++event->cpu == NR_CPUS) event->cpu = 0; snprintf(cmd, sizeof(cmd), "read %d\n", cpu); size = strlen(cmd); if (size > count) size = count; if (copy_to_user(buffer, cmd, size)) return -EFAULT; return size; } static struct file_operations salinfo_event_fops = { .open = salinfo_event_open, .read = salinfo_event_read, }; static int salinfo_log_open(struct inode *inode, struct file *file) { struct proc_dir_entry *entry = (struct proc_dir_entry *) inode->u.generic_ip; struct salinfo_data *data = entry->data; if (!suser()) return -EPERM; spin_lock(&data_lock); if (data->open) { spin_unlock(&data_lock); return -EBUSY; } data->open = 1; spin_unlock(&data_lock); return 0; } static int salinfo_log_release(struct inode *inode, struct file *file) { struct proc_dir_entry *entry = (struct proc_dir_entry *) inode->u.generic_ip; struct salinfo_data *data = entry->data; spin_lock(&data_lock); data->open = 0; spin_unlock(&data_lock); return 0; } static void call_on_cpu(int cpu, void (*fn)(void *), void *arg) { if (cpu == smp_processor_id()) (*fn)(arg); #ifdef CONFIG_SMP else if (cpu_online(cpu)) /* cpu may not have been validated */ smp_call_function_single(cpu, fn, arg, 0, 1); #endif } static void salinfo_log_read_cpu(void *context) { struct salinfo_log_data *info = context; struct salinfo_event *event = salinfo_event[info->type]; u64 size; size = ia64_sal_get_state_info_size(info->type); info->log_buffer = kmalloc(size, GFP_ATOMIC); if (!info->log_buffer) return; clear_bit(smp_processor_id(), &event->cpu_mask); info->log_size = ia64_sal_get_state_info(info->type, (u64 *) info->log_buffer); if (info->log_size) salinfo_log_wakeup(info->type); } static ssize_t salinfo_log_read(struct file *file, char *buffer, size_t count, loff_t *ppos) { struct inode *inode = file->f_dentry->d_inode; struct proc_dir_entry *entry = (struct proc_dir_entry *) inode->u.generic_ip; struct salinfo_data *data = entry->data; struct salinfo_log_data info; int ret; void *saldata; size_t size; info.type = data->type; info.log_buffer = 0; call_on_cpu(data->cpu, salinfo_log_read_cpu, &info); if (!info.log_buffer || *ppos >= info.log_size) { ret = 0; goto out; } saldata = info.log_buffer + file->f_pos; size = info.log_size - file->f_pos; if (size > count) size = count; if (copy_to_user(buffer, saldata, size)) { ret = -EFAULT; goto out; } *ppos += size; ret = size; out: kfree(info.log_buffer); return ret; } static void salinfo_log_clear_cpu(void *context) { struct salinfo_data *data = context; struct salinfo_event *event = salinfo_event[data->type]; struct salinfo_log_data info; clear_bit(smp_processor_id(), &event->cpu_mask); ia64_sal_clear_state_info(data->type); /* clearing one record may make another visible */ info.type = data->type; salinfo_log_read_cpu(&info); if (info.log_buffer && info.log_size) salinfo_log_wakeup(data->type); kfree(info.log_buffer); } static ssize_t salinfo_log_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) { struct inode *inode = file->f_dentry->d_inode; struct proc_dir_entry *entry = (struct proc_dir_entry *) inode->u.generic_ip; struct salinfo_data *data = entry->data; char cmd[32]; size_t size; int cpu; size = sizeof(cmd); if (count < size) size = count; if (copy_from_user(cmd, buffer, size)) return -EFAULT; if (sscanf(cmd, "read %d", &cpu) == 1) data->cpu = cpu; else if (sscanf(cmd, "clear %d", &cpu) == 1) call_on_cpu(cpu, salinfo_log_clear_cpu, data); return count; } static struct file_operations salinfo_data_fops = { .open = salinfo_log_open, .release = salinfo_log_release, .read = salinfo_log_read, .write = salinfo_log_write, }; static int __init salinfo_init(void) { struct proc_dir_entry *salinfo_dir; /* /proc/sal dir entry */ struct proc_dir_entry **sdir = salinfo_proc_entries; /* keeps track of every entry */ struct proc_dir_entry *dir, *entry; struct salinfo_event *event; struct salinfo_data *data; int i, j; salinfo_dir = proc_mkdir("sal", NULL); if (!salinfo_dir) return 0; for (i=0; i < NR_SALINFO_ENTRIES; i++) { /* pass the feature bit in question as misc data */ *sdir++ = create_proc_read_entry (salinfo_entries[i].name, 0, salinfo_dir, salinfo_read, (void *)salinfo_entries[i].feature); } for (i = 0; i < ARRAY_SIZE(salinfo_log_name); i++) { dir = proc_mkdir(salinfo_log_name[i], salinfo_dir); if (!dir) continue; entry = create_proc_entry("event", S_IRUSR, dir); if (!entry) continue; event = kmalloc(sizeof(*event), GFP_KERNEL); if (!event) continue; memset(event, 0, sizeof(*event)); event->type = i; init_waitqueue_head(&event->queue); salinfo_event[i] = event; /* we missed any events before now */ for (j = 0; j < NR_CPUS; j++) if (cpu_online(j)) set_bit(j, &event->cpu_mask); entry->data = event; entry->proc_fops = &salinfo_event_fops; *sdir++ = entry; entry = create_proc_entry("data", S_IRUSR | S_IWUSR, dir); if (!entry) continue; data = &salinfo_data[i]; data->type = i; entry->data = data; entry->proc_fops = &salinfo_data_fops; *sdir++ = entry; *sdir++ = dir; } *sdir++ = salinfo_dir; return 0; } static void __exit salinfo_exit(void) { int i = 0; for (i = 0; i < ARRAY_SIZE(salinfo_proc_entries); i++) { if (salinfo_proc_entries[i]) remove_proc_entry (salinfo_proc_entries[i]->name, NULL); } } /* * 'data' contains an integer that corresponds to the feature we're * testing */ static int salinfo_read(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; MOD_INC_USE_COUNT; len = sprintf(page, (sal_platform_features & (unsigned long)data) ? "1\n" : "0\n"); if (len <= off+count) *eof = 1; *start = page + off; len -= off; if (len>count) len = count; if (len<0) len = 0; MOD_DEC_USE_COUNT; return len; } module_init(salinfo_init); module_exit(salinfo_exit);