1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Hypervisor filesystem for Linux on s390 |
4 | * |
5 | * Diag 0C implementation |
6 | * |
7 | * Copyright IBM Corp. 2014 |
8 | */ |
9 | |
10 | #include <linux/slab.h> |
11 | #include <linux/cpu.h> |
12 | #include <asm/diag.h> |
13 | #include <asm/hypfs.h> |
14 | #include "hypfs.h" |
15 | |
16 | #define DBFS_D0C_HDR_VERSION 0 |
17 | |
18 | /* |
19 | * Get hypfs_diag0c_entry from CPU vector and store diag0c data |
20 | */ |
21 | static void diag0c_fn(void *data) |
22 | { |
23 | diag0c(((void **)data)[smp_processor_id()]); |
24 | } |
25 | |
26 | /* |
27 | * Allocate buffer and store diag 0c data |
28 | */ |
29 | static void *diag0c_store(unsigned int *count) |
30 | { |
31 | struct hypfs_diag0c_data *diag0c_data; |
32 | unsigned int cpu_count, cpu, i; |
33 | void **cpu_vec; |
34 | |
35 | cpus_read_lock(); |
36 | cpu_count = num_online_cpus(); |
37 | cpu_vec = kmalloc_array(num_possible_cpus(), size: sizeof(*cpu_vec), |
38 | GFP_KERNEL); |
39 | if (!cpu_vec) |
40 | goto fail_unlock_cpus; |
41 | /* Note: Diag 0c needs 8 byte alignment and real storage */ |
42 | diag0c_data = kzalloc(struct_size(diag0c_data, entry, cpu_count), |
43 | GFP_KERNEL | GFP_DMA); |
44 | if (!diag0c_data) |
45 | goto fail_kfree_cpu_vec; |
46 | i = 0; |
47 | /* Fill CPU vector for each online CPU */ |
48 | for_each_online_cpu(cpu) { |
49 | diag0c_data->entry[i].cpu = cpu; |
50 | cpu_vec[cpu] = &diag0c_data->entry[i++]; |
51 | } |
52 | /* Collect data all CPUs */ |
53 | on_each_cpu(func: diag0c_fn, info: cpu_vec, wait: 1); |
54 | *count = cpu_count; |
55 | kfree(objp: cpu_vec); |
56 | cpus_read_unlock(); |
57 | return diag0c_data; |
58 | |
59 | fail_kfree_cpu_vec: |
60 | kfree(objp: cpu_vec); |
61 | fail_unlock_cpus: |
62 | cpus_read_unlock(); |
63 | return ERR_PTR(error: -ENOMEM); |
64 | } |
65 | |
66 | /* |
67 | * Hypfs DBFS callback: Free diag 0c data |
68 | */ |
69 | static void dbfs_diag0c_free(const void *data) |
70 | { |
71 | kfree(objp: data); |
72 | } |
73 | |
74 | /* |
75 | * Hypfs DBFS callback: Create diag 0c data |
76 | */ |
77 | static int dbfs_diag0c_create(void **data, void **data_free_ptr, size_t *size) |
78 | { |
79 | struct hypfs_diag0c_data *diag0c_data; |
80 | unsigned int count; |
81 | |
82 | diag0c_data = diag0c_store(count: &count); |
83 | if (IS_ERR(ptr: diag0c_data)) |
84 | return PTR_ERR(ptr: diag0c_data); |
85 | memset(&diag0c_data->hdr, 0, sizeof(diag0c_data->hdr)); |
86 | store_tod_clock_ext((union tod_clock *)diag0c_data->hdr.tod_ext); |
87 | diag0c_data->hdr.len = count * sizeof(struct hypfs_diag0c_entry); |
88 | diag0c_data->hdr.version = DBFS_D0C_HDR_VERSION; |
89 | diag0c_data->hdr.count = count; |
90 | *data = diag0c_data; |
91 | *data_free_ptr = diag0c_data; |
92 | *size = diag0c_data->hdr.len + sizeof(struct hypfs_diag0c_hdr); |
93 | return 0; |
94 | } |
95 | |
96 | /* |
97 | * Hypfs DBFS file structure |
98 | */ |
99 | static struct hypfs_dbfs_file dbfs_file_0c = { |
100 | .name = "diag_0c" , |
101 | .data_create = dbfs_diag0c_create, |
102 | .data_free = dbfs_diag0c_free, |
103 | }; |
104 | |
105 | /* |
106 | * Initialize diag 0c interface for z/VM |
107 | */ |
108 | int __init hypfs_diag0c_init(void) |
109 | { |
110 | if (!MACHINE_IS_VM) |
111 | return 0; |
112 | hypfs_dbfs_create_file(df: &dbfs_file_0c); |
113 | return 0; |
114 | } |
115 | |
116 | /* |
117 | * Shutdown diag 0c interface for z/VM |
118 | */ |
119 | void hypfs_diag0c_exit(void) |
120 | { |
121 | if (!MACHINE_IS_VM) |
122 | return; |
123 | hypfs_dbfs_remove_file(df: &dbfs_file_0c); |
124 | } |
125 | |