diff options
Diffstat (limited to 'arch/s390/kernel/lgr.c')
-rw-r--r-- | arch/s390/kernel/lgr.c | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/arch/s390/kernel/lgr.c b/arch/s390/kernel/lgr.c new file mode 100644 index 000000000000..8431b92ca3ae --- /dev/null +++ b/arch/s390/kernel/lgr.c | |||
@@ -0,0 +1,200 @@ | |||
1 | /* | ||
2 | * Linux Guest Relocation (LGR) detection | ||
3 | * | ||
4 | * Copyright IBM Corp. 2012 | ||
5 | * Author(s): Michael Holzheu <holzheu@linux.vnet.ibm.com> | ||
6 | */ | ||
7 | |||
8 | #include <linux/module.h> | ||
9 | #include <linux/timer.h> | ||
10 | #include <linux/slab.h> | ||
11 | #include <asm/sysinfo.h> | ||
12 | #include <asm/ebcdic.h> | ||
13 | #include <asm/system.h> | ||
14 | #include <asm/debug.h> | ||
15 | #include <asm/ipl.h> | ||
16 | |||
17 | #define LGR_TIMER_INTERVAL_SECS (30 * 60) | ||
18 | #define VM_LEVEL_MAX 2 /* Maximum is 8, but we only record two levels */ | ||
19 | |||
20 | /* | ||
21 | * LGR info: Contains stfle and stsi data | ||
22 | */ | ||
23 | struct lgr_info { | ||
24 | /* Bit field with facility information: 4 DWORDs are stored */ | ||
25 | u64 stfle_fac_list[4]; | ||
26 | /* Level of system (1 = CEC, 2 = LPAR, 3 = z/VM */ | ||
27 | u32 level; | ||
28 | /* Level 1: CEC info (stsi 1.1.1) */ | ||
29 | char manufacturer[16]; | ||
30 | char type[4]; | ||
31 | char sequence[16]; | ||
32 | char plant[4]; | ||
33 | char model[16]; | ||
34 | /* Level 2: LPAR info (stsi 2.2.2) */ | ||
35 | u16 lpar_number; | ||
36 | char name[8]; | ||
37 | /* Level 3: VM info (stsi 3.2.2) */ | ||
38 | u8 vm_count; | ||
39 | struct { | ||
40 | char name[8]; | ||
41 | char cpi[16]; | ||
42 | } vm[VM_LEVEL_MAX]; | ||
43 | } __packed __aligned(8); | ||
44 | |||
45 | /* | ||
46 | * LGR globals | ||
47 | */ | ||
48 | static void *lgr_page; | ||
49 | static struct lgr_info lgr_info_last; | ||
50 | static struct lgr_info lgr_info_cur; | ||
51 | static struct debug_info *lgr_dbf; | ||
52 | |||
53 | /* | ||
54 | * Return number of valid stsi levels | ||
55 | */ | ||
56 | static inline int stsi_0(void) | ||
57 | { | ||
58 | int rc = stsi(NULL, 0, 0, 0); | ||
59 | |||
60 | return rc == -ENOSYS ? rc : (((unsigned int) rc) >> 28); | ||
61 | } | ||
62 | |||
63 | /* | ||
64 | * Copy buffer and then convert it to ASCII | ||
65 | */ | ||
66 | static void cpascii(char *dst, char *src, int size) | ||
67 | { | ||
68 | memcpy(dst, src, size); | ||
69 | EBCASC(dst, size); | ||
70 | } | ||
71 | |||
72 | /* | ||
73 | * Fill LGR info with 1.1.1 stsi data | ||
74 | */ | ||
75 | static void lgr_stsi_1_1_1(struct lgr_info *lgr_info) | ||
76 | { | ||
77 | struct sysinfo_1_1_1 *si = lgr_page; | ||
78 | |||
79 | if (stsi(si, 1, 1, 1) == -ENOSYS) | ||
80 | return; | ||
81 | cpascii(lgr_info->manufacturer, si->manufacturer, | ||
82 | sizeof(si->manufacturer)); | ||
83 | cpascii(lgr_info->type, si->type, sizeof(si->type)); | ||
84 | cpascii(lgr_info->model, si->model, sizeof(si->model)); | ||
85 | cpascii(lgr_info->sequence, si->sequence, sizeof(si->sequence)); | ||
86 | cpascii(lgr_info->plant, si->plant, sizeof(si->plant)); | ||
87 | } | ||
88 | |||
89 | /* | ||
90 | * Fill LGR info with 2.2.2 stsi data | ||
91 | */ | ||
92 | static void lgr_stsi_2_2_2(struct lgr_info *lgr_info) | ||
93 | { | ||
94 | struct sysinfo_2_2_2 *si = lgr_page; | ||
95 | |||
96 | if (stsi(si, 2, 2, 2) == -ENOSYS) | ||
97 | return; | ||
98 | cpascii(lgr_info->name, si->name, sizeof(si->name)); | ||
99 | memcpy(&lgr_info->lpar_number, &si->lpar_number, | ||
100 | sizeof(lgr_info->lpar_number)); | ||
101 | } | ||
102 | |||
103 | /* | ||
104 | * Fill LGR info with 3.2.2 stsi data | ||
105 | */ | ||
106 | static void lgr_stsi_3_2_2(struct lgr_info *lgr_info) | ||
107 | { | ||
108 | struct sysinfo_3_2_2 *si = lgr_page; | ||
109 | int i; | ||
110 | |||
111 | if (stsi(si, 3, 2, 2) == -ENOSYS) | ||
112 | return; | ||
113 | for (i = 0; i < min_t(u8, si->count, VM_LEVEL_MAX); i++) { | ||
114 | cpascii(lgr_info->vm[i].name, si->vm[i].name, | ||
115 | sizeof(si->vm[i].name)); | ||
116 | cpascii(lgr_info->vm[i].cpi, si->vm[i].cpi, | ||
117 | sizeof(si->vm[i].cpi)); | ||
118 | } | ||
119 | lgr_info->vm_count = si->count; | ||
120 | } | ||
121 | |||
122 | /* | ||
123 | * Fill LGR info with current data | ||
124 | */ | ||
125 | static void lgr_info_get(struct lgr_info *lgr_info) | ||
126 | { | ||
127 | memset(lgr_info, 0, sizeof(*lgr_info)); | ||
128 | stfle(lgr_info->stfle_fac_list, ARRAY_SIZE(lgr_info->stfle_fac_list)); | ||
129 | lgr_info->level = stsi_0(); | ||
130 | if (lgr_info->level == -ENOSYS) | ||
131 | return; | ||
132 | if (lgr_info->level >= 1) | ||
133 | lgr_stsi_1_1_1(lgr_info); | ||
134 | if (lgr_info->level >= 2) | ||
135 | lgr_stsi_2_2_2(lgr_info); | ||
136 | if (lgr_info->level >= 3) | ||
137 | lgr_stsi_3_2_2(lgr_info); | ||
138 | } | ||
139 | |||
140 | /* | ||
141 | * Check if LGR info has changed and if yes log new LGR info to s390dbf | ||
142 | */ | ||
143 | void lgr_info_log(void) | ||
144 | { | ||
145 | static DEFINE_SPINLOCK(lgr_info_lock); | ||
146 | unsigned long flags; | ||
147 | |||
148 | if (!spin_trylock_irqsave(&lgr_info_lock, flags)) | ||
149 | return; | ||
150 | lgr_info_get(&lgr_info_cur); | ||
151 | if (memcmp(&lgr_info_last, &lgr_info_cur, sizeof(lgr_info_cur)) != 0) { | ||
152 | debug_event(lgr_dbf, 1, &lgr_info_cur, sizeof(lgr_info_cur)); | ||
153 | lgr_info_last = lgr_info_cur; | ||
154 | } | ||
155 | spin_unlock_irqrestore(&lgr_info_lock, flags); | ||
156 | } | ||
157 | EXPORT_SYMBOL_GPL(lgr_info_log); | ||
158 | |||
159 | static void lgr_timer_set(void); | ||
160 | |||
161 | /* | ||
162 | * LGR timer callback | ||
163 | */ | ||
164 | static void lgr_timer_fn(unsigned long ignored) | ||
165 | { | ||
166 | lgr_info_log(); | ||
167 | lgr_timer_set(); | ||
168 | } | ||
169 | |||
170 | static struct timer_list lgr_timer = | ||
171 | TIMER_DEFERRED_INITIALIZER(lgr_timer_fn, 0, 0); | ||
172 | |||
173 | /* | ||
174 | * Setup next LGR timer | ||
175 | */ | ||
176 | static void lgr_timer_set(void) | ||
177 | { | ||
178 | mod_timer(&lgr_timer, jiffies + LGR_TIMER_INTERVAL_SECS * HZ); | ||
179 | } | ||
180 | |||
181 | /* | ||
182 | * Initialize LGR: Add s390dbf, write initial lgr_info and setup timer | ||
183 | */ | ||
184 | static int __init lgr_init(void) | ||
185 | { | ||
186 | lgr_page = (void *) __get_free_pages(GFP_KERNEL, 0); | ||
187 | if (!lgr_page) | ||
188 | return -ENOMEM; | ||
189 | lgr_dbf = debug_register("lgr", 1, 1, sizeof(struct lgr_info)); | ||
190 | if (!lgr_dbf) { | ||
191 | free_page((unsigned long) lgr_page); | ||
192 | return -ENOMEM; | ||
193 | } | ||
194 | debug_register_view(lgr_dbf, &debug_hex_ascii_view); | ||
195 | lgr_info_get(&lgr_info_last); | ||
196 | debug_event(lgr_dbf, 1, &lgr_info_last, sizeof(lgr_info_last)); | ||
197 | lgr_timer_set(); | ||
198 | return 0; | ||
199 | } | ||
200 | module_init(lgr_init); | ||