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"
42 struct path_cxt
*sysmem
; /* _PATH_SYS_MEMORY handler */
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"));
126 if (ul_path_readf_buffer(desc
->sysmem
, line
, sizeof(line
), "%s/state", name
) > 0
127 && strncmp(onoff
, line
, 6) == 0)
130 if (desc
->have_zones
) {
131 ul_path_readf_buffer(desc
->sysmem
, line
, sizeof(line
), "%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
= ul_path_writef_string(desc
->sysmem
, onoff
, "%s/state", name
);
149 if (rc
!= 0 && 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 if (ul_path_readf_buffer(desc
->sysmem
, line
, sizeof(line
), "%s/state", name
) > 0
204 && 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 ul_path_readf_buffer(desc
->sysmem
, line
, sizeof(line
), "%s/valid_zones", name
);
216 zn
= zone_names
[zone_id
];
217 if (enable
&& !strcasestr(line
, zn
)) {
218 warnx(_("%s enable failed: Zone mismatch"), str
);
221 if (!enable
&& strncasecmp(line
, zn
, strlen(zn
))) {
222 warnx(_("%s disable failed: Zone mismatch"), str
);
226 /* By default, use zone Movable for online, if valid */
227 if (strcasestr(line
, zone_names
[ZONE_MOVABLE
]))
228 onoff
= "online_movable";
234 rc
= ul_path_writef_string(desc
->sysmem
, onoff
, "%s/state", name
);
237 warn(_("%s enable failed"), str
);
239 warn(_("%s disable failed"), str
);
240 } else if (desc
->verbose
) {
242 fprintf(stdout
, _("%s enabled\n"), str
);
244 fprintf(stdout
, _("%s disabled\n"), str
);
249 return todo
== 0 ? 0 : todo
== desc
->end
- desc
->start
+ 1 ? -1 : 1;
252 static int filter(const struct dirent
*de
)
254 if (strncmp("memory", de
->d_name
, 6))
256 return isdigit_string(de
->d_name
+ 6);
259 static void read_info(struct chmem_desc
*desc
)
263 desc
->ndirs
= scandir(_PATH_SYS_MEMORY
, &desc
->dirs
, filter
, versionsort
);
264 if (desc
->ndirs
<= 0)
265 err(EXIT_FAILURE
, _("Failed to read %s"), _PATH_SYS_MEMORY
);
266 ul_path_read_buffer(desc
->sysmem
, line
, sizeof(line
), "block_size_bytes");
267 desc
->block_size
= strtoumax(line
, NULL
, 16);
270 static void parse_single_param(struct chmem_desc
*desc
, char *str
)
272 if (desc
->use_blocks
) {
273 desc
->start
= strtou64_or_err(str
, _("Failed to parse block number"));
274 desc
->end
= desc
->start
;
278 desc
->size
= strtosize_or_err(str
, _("Failed to parse size"));
279 if (isdigit(str
[strlen(str
) - 1]))
280 desc
->size
*= 1024*1024;
281 if (desc
->size
% desc
->block_size
) {
282 errx(EXIT_FAILURE
, _("Size must be aligned to memory block size (%s)"),
283 size_to_human_string(SIZE_SUFFIX_1LETTER
, desc
->block_size
));
285 desc
->size
/= desc
->block_size
;
288 static void parse_range_param(struct chmem_desc
*desc
, char *start
, char *end
)
290 if (desc
->use_blocks
) {
291 desc
->start
= strtou64_or_err(start
, _("Failed to parse start"));
292 desc
->end
= strtou64_or_err(end
, _("Failed to parse end"));
295 if (strlen(start
) < 2 || start
[1] != 'x')
296 errx(EXIT_FAILURE
, _("Invalid start address format: %s"), start
);
297 if (strlen(end
) < 2 || end
[1] != 'x')
298 errx(EXIT_FAILURE
, _("Invalid end address format: %s"), end
);
299 desc
->start
= strtox64_or_err(start
, _("Failed to parse start address"));
300 desc
->end
= strtox64_or_err(end
, _("Failed to parse end address"));
301 if (desc
->start
% desc
->block_size
|| (desc
->end
+ 1) % desc
->block_size
) {
303 _("Start address and (end address + 1) must be aligned to "
304 "memory block size (%s)"),
305 size_to_human_string(SIZE_SUFFIX_1LETTER
, desc
->block_size
));
307 desc
->start
/= desc
->block_size
;
308 desc
->end
/= desc
->block_size
;
311 static void parse_parameter(struct chmem_desc
*desc
, char *param
)
315 split
= strv_split(param
, "-");
316 if (strv_length(split
) > 2)
317 errx(EXIT_FAILURE
, _("Invalid parameter: %s"), param
);
318 if (strv_length(split
) == 1)
319 parse_single_param(desc
, split
[0]);
321 parse_range_param(desc
, split
[0], split
[1]);
323 if (desc
->start
> desc
->end
)
324 errx(EXIT_FAILURE
, _("Invalid range: %s"), param
);
327 static void __attribute__((__noreturn__
)) usage(void)
332 fputs(USAGE_HEADER
, out
);
333 fprintf(out
, _(" %s [options] [SIZE|RANGE|BLOCKRANGE]\n"), program_invocation_short_name
);
335 fputs(USAGE_SEPARATOR
, out
);
336 fputs(_("Set a particular size or range of memory online or offline.\n"), out
);
338 fputs(USAGE_OPTIONS
, out
);
339 fputs(_(" -e, --enable enable memory\n"), out
);
340 fputs(_(" -d, --disable disable memory\n"), out
);
341 fputs(_(" -b, --blocks use memory blocks\n"), out
);
342 fputs(_(" -z, --zone <name> select memory zone (see below)\n"), out
);
343 fputs(_(" -v, --verbose verbose output\n"), out
);
344 printf(USAGE_HELP_OPTIONS(20));
346 fputs(_("\nSupported zones:\n"), out
);
347 for (i
= 0; i
< ARRAY_SIZE(zone_names
); i
++)
348 fprintf(out
, " %s\n", zone_names
[i
]);
350 printf(USAGE_MAN_TAIL("chmem(8)"));
355 int main(int argc
, char **argv
)
357 struct chmem_desc _desc
= { 0 }, *desc
= &_desc
;
358 int cmd
= CMD_NONE
, zone_id
= -1;
362 static const struct option longopts
[] = {
363 {"block", no_argument
, NULL
, 'b'},
364 {"disable", no_argument
, NULL
, 'd'},
365 {"enable", no_argument
, NULL
, 'e'},
366 {"help", no_argument
, NULL
, 'h'},
367 {"verbose", no_argument
, NULL
, 'v'},
368 {"version", no_argument
, NULL
, 'V'},
369 {"zone", required_argument
, NULL
, 'z'},
373 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
377 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
379 setlocale(LC_ALL
, "");
380 bindtextdomain(PACKAGE
, LOCALEDIR
);
382 close_stdout_atexit();
384 ul_path_init_debug();
385 desc
->sysmem
= ul_new_path(_PATH_SYS_MEMORY
);
387 err(EXIT_FAILURE
, _("failed to initialize %s handler"), _PATH_SYS_MEMORY
);
391 while ((c
= getopt_long(argc
, argv
, "bdehvVz:", longopts
, NULL
)) != -1) {
393 err_exclusive_options(c
, longopts
, excl
, excl_st
);
397 cmd
= CMD_MEMORY_DISABLE
;
400 cmd
= CMD_MEMORY_ENABLE
;
403 desc
->use_blocks
= 1;
409 zone
= xstrdup(optarg
);
415 print_version(EXIT_SUCCESS
);
417 errtryhelp(EXIT_FAILURE
);
421 if ((argc
== 1) || (argc
!= optind
+ 1) || (cmd
== CMD_NONE
)) {
422 warnx(_("bad usage"));
423 errtryhelp(EXIT_FAILURE
);
426 parse_parameter(desc
, argv
[optind
]);
429 /* The valid_zones sysfs attribute was introduced with kernel 3.18 */
430 if (ul_path_access(desc
->sysmem
, F_OK
, "memory0/valid_zones") == 0)
431 desc
->have_zones
= 1;
433 warnx(_("zone ignored, no valid_zones sysfs attribute present"));
435 if (zone
&& desc
->have_zones
) {
436 zone_id
= zone_name_to_id(zone
);
438 warnx(_("unknown memory zone: %s"), zone
);
439 errtryhelp(EXIT_FAILURE
);
444 rc
= chmem_size(desc
, cmd
== CMD_MEMORY_ENABLE
? 1 : 0, zone_id
);
446 rc
= chmem_range(desc
, cmd
== CMD_MEMORY_ENABLE
? 1 : 0, zone_id
);
448 ul_unref_path(desc
->sysmem
);
450 return rc
== 0 ? EXIT_SUCCESS
:
451 rc
< 0 ? EXIT_FAILURE
: CHMEM_EXIT_SOMEOK
;