2 * chmem - Memory configuration tool
4 * Copyright IBM Corp. 2016
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it would be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
33 #include "closestream.h"
36 /* partial success, otherwise we return regular EXIT_{SUCCESS,FAILURE} */
37 #define CHMEM_EXIT_SOMEOK 64
39 #define _PATH_SYS_MEMORY "/sys/devices/system/memory"
40 #define _PATH_SYS_MEMORY_BLOCK_SIZE _PATH_SYS_MEMORY "/block_size_bytes"
49 unsigned int use_blocks
: 1;
50 unsigned int is_size
: 1;
51 unsigned int verbose
: 1;
52 unsigned int have_zones
: 1;
56 CMD_MEMORY_ENABLE
= 0,
70 static char *zone_names
[] = {
72 [ZONE_DMA32
] = "DMA32",
73 [ZONE_NORMAL
] = "Normal",
74 [ZONE_HIGHMEM
] = "Highmem",
75 [ZONE_MOVABLE
] = "Movable",
76 [ZONE_DEVICE
] = "Device",
80 * name must be null-terminated
82 static int zone_name_to_id(const char *name
)
86 for (i
= 0; i
< ARRAY_SIZE(zone_names
); i
++) {
87 if (!strcasecmp(name
, zone_names
[i
]))
93 static void idxtostr(struct chmem_desc
*desc
, uint64_t idx
, char *buf
, size_t bufsz
)
97 start
= idx
* desc
->block_size
;
98 end
= start
+ desc
->block_size
- 1;
100 _("Memory Block %"PRIu64
" (0x%016"PRIx64
"-0x%016"PRIx64
")"),
104 static int chmem_size(struct chmem_desc
*desc
, int enable
, int zone_id
)
106 char *name
, *onoff
, line
[BUFSIZ
], str
[BUFSIZ
];
107 uint64_t size
, index
;
112 onoff
= enable
? "online" : "offline";
113 i
= enable
? 0 : desc
->ndirs
- 1;
115 if (enable
&& zone_id
>= 0) {
116 if (zone_id
== ZONE_MOVABLE
)
117 onoff
= "online_movable";
119 onoff
= "online_kernel";
122 for (; i
>= 0 && i
< desc
->ndirs
&& size
; i
+= enable
? 1 : -1) {
123 name
= desc
->dirs
[i
]->d_name
;
124 index
= strtou64_or_err(name
+ 6, _("Failed to parse index"));
125 path_read_str(line
, sizeof(line
), _PATH_SYS_MEMORY
"/%s/state", name
);
126 if (strncmp(onoff
, line
, 6) == 0)
129 if (desc
->have_zones
) {
130 path_read_str(line
, sizeof(line
),
131 _PATH_SYS_MEMORY
"/%s/valid_zones", name
);
133 zn
= zone_names
[zone_id
];
134 if (enable
&& !strcasestr(line
, zn
))
136 if (!enable
&& strncasecmp(line
, zn
, strlen(zn
)))
139 /* By default, use zone Movable for online, if valid */
140 if (strcasestr(line
, zone_names
[ZONE_MOVABLE
]))
141 onoff
= "online_movable";
147 idxtostr(desc
, index
, str
, sizeof(str
));
148 rc
= path_write_str(onoff
, _PATH_SYS_MEMORY
"/%s/state", name
);
149 if (rc
== -1 && desc
->verbose
) {
151 fprintf(stdout
, _("%s enable failed\n"), str
);
153 fprintf(stdout
, _("%s disable failed\n"), str
);
154 } else if (rc
== 0 && desc
->verbose
) {
156 fprintf(stdout
, _("%s enabled\n"), str
);
158 fprintf(stdout
, _("%s disabled\n"), str
);
167 bytes
= (desc
->size
- size
) * desc
->block_size
;
168 sizestr
= size_to_human_string(SIZE_SUFFIX_1LETTER
, bytes
);
170 warnx(_("Could only enable %s of memory"), sizestr
);
172 warnx(_("Could only disable %s of memory"), sizestr
);
175 return size
== 0 ? 0 : size
== desc
->size
? -1 : 1;
178 static int chmem_range(struct chmem_desc
*desc
, int enable
, int zone_id
)
180 char *name
, *onoff
, line
[BUFSIZ
], str
[BUFSIZ
];
181 uint64_t index
, todo
;
185 todo
= desc
->end
- desc
->start
+ 1;
186 onoff
= enable
? "online" : "offline";
188 if (enable
&& zone_id
>= 0) {
189 if (zone_id
== ZONE_MOVABLE
)
190 onoff
= "online_movable";
192 onoff
= "online_kernel";
195 for (i
= 0; i
< desc
->ndirs
; i
++) {
196 name
= desc
->dirs
[i
]->d_name
;
197 index
= strtou64_or_err(name
+ 6, _("Failed to parse index"));
198 if (index
< desc
->start
)
200 if (index
> desc
->end
)
202 idxtostr(desc
, index
, str
, sizeof(str
));
203 path_read_str(line
, sizeof(line
), _PATH_SYS_MEMORY
"/%s/state", name
);
204 if (strncmp(onoff
, line
, 6) == 0) {
205 if (desc
->verbose
&& enable
)
206 fprintf(stdout
, _("%s already enabled\n"), str
);
207 else if (desc
->verbose
&& !enable
)
208 fprintf(stdout
, _("%s already disabled\n"), str
);
213 if (desc
->have_zones
) {
214 path_read_str(line
, sizeof(line
),
215 _PATH_SYS_MEMORY
"/%s/valid_zones", name
);
217 zn
= zone_names
[zone_id
];
218 if (enable
&& !strcasestr(line
, zn
)) {
219 warnx(_("%s enable failed: Zone mismatch"), str
);
222 if (!enable
&& strncasecmp(line
, zn
, strlen(zn
))) {
223 warnx(_("%s disable failed: Zone mismatch"), str
);
227 /* By default, use zone Movable for online, if valid */
228 if (strcasestr(line
, zone_names
[ZONE_MOVABLE
]))
229 onoff
= "online_movable";
235 rc
= path_write_str(onoff
, _PATH_SYS_MEMORY
"/%s/state", name
);
238 warn(_("%s enable failed"), str
);
240 warn(_("%s disable failed"), str
);
241 } else if (desc
->verbose
) {
243 fprintf(stdout
, _("%s enabled\n"), str
);
245 fprintf(stdout
, _("%s disabled\n"), str
);
250 return todo
== 0 ? 0 : todo
== desc
->end
- desc
->start
+ 1 ? -1 : 1;
253 static int filter(const struct dirent
*de
)
255 if (strncmp("memory", de
->d_name
, 6))
257 return isdigit_string(de
->d_name
+ 6);
260 static void read_info(struct chmem_desc
*desc
)
264 desc
->ndirs
= scandir(_PATH_SYS_MEMORY
, &desc
->dirs
, filter
, versionsort
);
265 if (desc
->ndirs
<= 0)
266 err(EXIT_FAILURE
, _("Failed to read %s"), _PATH_SYS_MEMORY
);
267 path_read_str(line
, sizeof(line
), _PATH_SYS_MEMORY_BLOCK_SIZE
);
268 desc
->block_size
= strtoumax(line
, NULL
, 16);
271 static void parse_single_param(struct chmem_desc
*desc
, char *str
)
273 if (desc
->use_blocks
) {
274 desc
->start
= strtou64_or_err(str
, _("Failed to parse block number"));
275 desc
->end
= desc
->start
;
279 desc
->size
= strtosize_or_err(str
, _("Failed to parse size"));
280 if (isdigit(str
[strlen(str
) - 1]))
281 desc
->size
*= 1024*1024;
282 if (desc
->size
% desc
->block_size
) {
283 errx(EXIT_FAILURE
, _("Size must be aligned to memory block size (%s)"),
284 size_to_human_string(SIZE_SUFFIX_1LETTER
, desc
->block_size
));
286 desc
->size
/= desc
->block_size
;
289 static void parse_range_param(struct chmem_desc
*desc
, char *start
, char *end
)
291 if (desc
->use_blocks
) {
292 desc
->start
= strtou64_or_err(start
, _("Failed to parse start"));
293 desc
->end
= strtou64_or_err(end
, _("Failed to parse end"));
296 if (strlen(start
) < 2 || start
[1] != 'x')
297 errx(EXIT_FAILURE
, _("Invalid start address format: %s"), start
);
298 if (strlen(end
) < 2 || end
[1] != 'x')
299 errx(EXIT_FAILURE
, _("Invalid end address format: %s"), end
);
300 desc
->start
= strtox64_or_err(start
, _("Failed to parse start address"));
301 desc
->end
= strtox64_or_err(end
, _("Failed to parse end address"));
302 if (desc
->start
% desc
->block_size
|| (desc
->end
+ 1) % desc
->block_size
) {
304 _("Start address and (end address + 1) must be aligned to "
305 "memory block size (%s)"),
306 size_to_human_string(SIZE_SUFFIX_1LETTER
, desc
->block_size
));
308 desc
->start
/= desc
->block_size
;
309 desc
->end
/= desc
->block_size
;
312 static void parse_parameter(struct chmem_desc
*desc
, char *param
)
316 split
= strv_split(param
, "-");
317 if (strv_length(split
) > 2)
318 errx(EXIT_FAILURE
, _("Invalid parameter: %s"), param
);
319 if (strv_length(split
) == 1)
320 parse_single_param(desc
, split
[0]);
322 parse_range_param(desc
, split
[0], split
[1]);
324 if (desc
->start
> desc
->end
)
325 errx(EXIT_FAILURE
, _("Invalid range: %s"), param
);
328 static void __attribute__((__noreturn__
)) usage(void)
333 fputs(USAGE_HEADER
, out
);
334 fprintf(out
, _(" %s [options] [SIZE|RANGE|BLOCKRANGE]\n"), program_invocation_short_name
);
336 fputs(USAGE_SEPARATOR
, out
);
337 fputs(_("Set a particular size or range of memory online or offline.\n"), out
);
339 fputs(USAGE_OPTIONS
, out
);
340 fputs(_(" -e, --enable enable memory\n"), out
);
341 fputs(_(" -d, --disable disable memory\n"), out
);
342 fputs(_(" -b, --blocks use memory blocks\n"), out
);
343 fputs(_(" -z, --zone <name> select memory zone (see below)\n"), out
);
344 fputs(_(" -v, --verbose verbose output\n"), out
);
345 printf(USAGE_HELP_OPTIONS(20));
347 fputs(_("\nSupported zones:\n"), out
);
348 for (i
= 0; i
< ARRAY_SIZE(zone_names
); i
++)
349 fprintf(out
, " %s\n", zone_names
[i
]);
351 printf(USAGE_MAN_TAIL("chmem(8)"));
356 int main(int argc
, char **argv
)
358 struct chmem_desc _desc
= { }, *desc
= &_desc
;
359 int cmd
= CMD_NONE
, zone_id
= -1;
363 static const struct option longopts
[] = {
364 {"block", no_argument
, NULL
, 'b'},
365 {"disable", no_argument
, NULL
, 'd'},
366 {"enable", no_argument
, NULL
, 'e'},
367 {"help", no_argument
, NULL
, 'h'},
368 {"verbose", no_argument
, NULL
, 'v'},
369 {"version", no_argument
, NULL
, 'V'},
370 {"zone", required_argument
, NULL
, 'z'},
374 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
378 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
380 setlocale(LC_ALL
, "");
381 bindtextdomain(PACKAGE
, LOCALEDIR
);
383 atexit(close_stdout
);
387 while ((c
= getopt_long(argc
, argv
, "bdehvVz:", longopts
, NULL
)) != -1) {
389 err_exclusive_options(c
, longopts
, excl
, excl_st
);
393 cmd
= CMD_MEMORY_DISABLE
;
396 cmd
= CMD_MEMORY_ENABLE
;
399 desc
->use_blocks
= 1;
408 printf(UTIL_LINUX_VERSION
);
411 zone
= xstrdup(optarg
);
414 errtryhelp(EXIT_FAILURE
);
418 if ((argc
== 1) || (argc
!= optind
+ 1) || (cmd
== CMD_NONE
)) {
419 warnx(_("bad usage"));
420 errtryhelp(EXIT_FAILURE
);
423 parse_parameter(desc
, argv
[optind
]);
425 /* The valid_zones sysfs attribute was introduced with kernel 3.18 */
426 if (path_exist(_PATH_SYS_MEMORY
"/memory0/valid_zones"))
427 desc
->have_zones
= 1;
429 warnx(_("zone ignored, no valid_zones sysfs attribute present"));
431 if (zone
&& desc
->have_zones
) {
432 zone_id
= zone_name_to_id(zone
);
434 warnx(_("unknown memory zone: %s"), zone
);
435 errtryhelp(EXIT_FAILURE
);
440 rc
= chmem_size(desc
, cmd
== CMD_MEMORY_ENABLE
? 1 : 0, zone_id
);
442 rc
= chmem_range(desc
, cmd
== CMD_MEMORY_ENABLE
? 1 : 0, zone_id
);
444 return rc
== 0 ? EXIT_SUCCESS
:
445 rc
< 0 ? EXIT_FAILURE
: CHMEM_EXIT_SOMEOK
;