/* * Hypervisor filesystem for Linux on s390 * * Diag 0C implementation * * Copyright IBM Corp. 2014 */ #include <linux/slab.h> #include <linux/cpu.h> #include <asm/diag.h> #include <asm/hypfs.h> #include "hypfs.h" #define DBFS_D0C_HDR_VERSION 0 /* * Execute diagnose 0c in 31 bit mode */ static void diag0c(struct hypfs_diag0c_entry *entry) { diag_stat_inc(DIAG_STAT_X00C); asm volatile ( " sam31\n" " diag %0,%0,0x0c\n" " sam64\n" : /* no output register */ : "a" (entry) : "memory"); } /* * Get hypfs_diag0c_entry from CPU vector and store diag0c data */ static void diag0c_fn(void *data) { diag0c(((void **) data)[smp_processor_id()]); } /* * Allocate buffer and store diag 0c data */ static void *diag0c_store(unsigned int *count) { struct hypfs_diag0c_data *diag0c_data; unsigned int cpu_count, cpu, i; void **cpu_vec; get_online_cpus(); cpu_count = num_online_cpus(); cpu_vec = kmalloc(sizeof(*cpu_vec) * num_possible_cpus(), GFP_KERNEL); if (!cpu_vec) goto fail_put_online_cpus; /* Note: Diag 0c needs 8 byte alignment and real storage */ diag0c_data = kzalloc(sizeof(struct hypfs_diag0c_hdr) + cpu_count * sizeof(struct hypfs_diag0c_entry), GFP_KERNEL | GFP_DMA); if (!diag0c_data) goto fail_kfree_cpu_vec; i = 0; /* Fill CPU vector for each online CPU */ for_each_online_cpu(cpu) { diag0c_data->entry[i].cpu = cpu; cpu_vec[cpu] = &diag0c_data->entry[i++]; } /* Collect data all CPUs */ on_each_cpu(diag0c_fn, cpu_vec, 1); *count = cpu_count; kfree(cpu_vec); put_online_cpus(); return diag0c_data; fail_kfree_cpu_vec: kfree(cpu_vec); fail_put_online_cpus: put_online_cpus(); return ERR_PTR(-ENOMEM); } /* * Hypfs DBFS callback: Free diag 0c data */ static void dbfs_diag0c_free(const void *data) { kfree(data); } /* * Hypfs DBFS callback: Create diag 0c data */ static int dbfs_diag0c_create(void **data, void **data_free_ptr, size_t *size) { struct hypfs_diag0c_data *diag0c_data; unsigned int count; diag0c_data = diag0c_store(&count); if (IS_ERR(diag0c_data)) return PTR_ERR(diag0c_data); memset(&diag0c_data->hdr, 0, sizeof(diag0c_data->hdr)); get_tod_clock_ext(diag0c_data->hdr.tod_ext); diag0c_data->hdr.len = count * sizeof(struct hypfs_diag0c_entry); diag0c_data->hdr.version = DBFS_D0C_HDR_VERSION; diag0c_data->hdr.count = count; *data = diag0c_data; *data_free_ptr = diag0c_data; *size = diag0c_data->hdr.len + sizeof(struct hypfs_diag0c_hdr); return 0; } /* * Hypfs DBFS file structure */ static struct hypfs_dbfs_file dbfs_file_0c = { .name = "diag_0c", .data_create = dbfs_diag0c_create, .data_free = dbfs_diag0c_free, }; /* * Initialize diag 0c interface for z/VM */ int __init hypfs_diag0c_init(void) { if (!MACHINE_IS_VM) return 0; return hypfs_dbfs_create_file(&dbfs_file_0c); } /* * Shutdown diag 0c interface for z/VM */ void hypfs_diag0c_exit(void) { if (!MACHINE_IS_VM) return; hypfs_dbfs_remove_file(&dbfs_file_0c); }