Summary:     lsmem/chmem: Tools to manage memory hotplug.
Description: With lsmem, you can display the online status of all available
             memory. With chmem, you can set memory online or offline.

---
 README         |    2 
 zconf/Makefile |   17 ++
 zconf/chmem    |  325 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 zconf/chmem.8  |   71 ++++++++++++
 zconf/lsmem    |  158 +++++++++++++++++++++++++++
 zconf/lsmem.8  |   69 ++++++++++++
 6 files changed, 639 insertions(+), 3 deletions(-)

--- a/README
+++ b/README
@@ -110,6 +110,8 @@ s390-tools (1.8.0)
      - chzcrypt:   Modify zcrypt configuration.
      - znetconf:   List and configure network devices for System z network
                    adapters.
+     - lsmem:      Display the online status of the available memory.
+     - chmem:      Set hotplug memory online or offline.
 
    * dumpconf:
      Allows to configure the dump device used for system dump in case a kernel
--- a/zconf/Makefile
+++ b/zconf/Makefile
@@ -5,14 +5,15 @@ include ../common.mak
 
 SCRIPTS	= lsdasd lstape lscss chccwdev lsqeth lszfcp lschp chchp lszcrypt \
 	  chzcrypt lsluns znetconf
+USRSBIN_SCRIPTS = lsmem chmem
 MANPAGES= lsdasd.8 lstape.8 lscss.8 chccwdev.8 lsqeth.8 lszfcp.8 lschp.8 \
-	  chchp.8 lszcrypt.8 chzcrypt.8 lsluns.8 znetconf.8
+	  chchp.8 lszcrypt.8 chzcrypt.8 lsluns.8 znetconf.8 chmem.8 lsmem.8
 
 all:
 
 clean:
 
-install:	install-scripts install-manpages
+install:	install-scripts install-manpages install-usrsbin-scripts
 	$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 lsznet.raw $(TOOLS_LIBDIR)
 	$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 znetcontrolunits \
 		$(TOOLS_LIBDIR)
@@ -27,6 +28,16 @@ install-scripts:	$(SCRIPTS)
 		chmod 755 $(BINDIR)/$$i; \
 	done
 
+install-usrsbin-scripts:	$(USRSBIN_SCRIPTS)
+	@for i in $^; do \
+		echo "Installing $$i; to $(USRSBINDIR)"; \
+		cat $$i | \
+		sed -e 's+%S390_TOOLS_VERSION%+$(S390_TOOLS_RELEASE)+' \
+		>$(USRSBINDIR)/$$i; \
+		chown $(OWNER).$(GROUP) $(USRSBINDIR)/$$i; \
+		chmod 755 $(USRSBINDIR)/$$i; \
+	done
+
 install-manpages:	$(MANPAGES)
 	@if [ ! -d $(MANDIR) ]; then \
 		mkdir -p $(MANDIR)/man8; \
@@ -40,4 +51,4 @@ install-manpages:	$(MANPAGES)
 		install -o $(OWNER) -g $(GROUP) -m 644 $$i $(MANDIR)/man8; \
 	done
 
-.PHONY: all install clean install-scripts install-manpages
+.PHONY: all install clean install-scripts install-manpages install-usrsbin-scripts
--- /dev/null
+++ b/zconf/chmem
@@ -0,0 +1,325 @@
+#!/usr/bin/perl
+###############################################################################
+# chmem - script to show memory hotplug status.
+#
+# Copyright IBM Corp. 2010
+# Author(s): Gerald Schaefer <gerald.schaefer@de.ibm.com>
+###############################################################################
+
+use strict;
+use warnings;
+use Getopt::Long qw(:config no_ignore_case no_auto_abbrev);
+use File::Basename;
+
+my $script_name = fileparse($0);
+my $online = 0;
+my $offline = 0;
+my $memdir = "/sys/devices/system/memory";
+my $block_size = 0;
+my $total_blocks = 0;
+my $devices = {};
+my $dev_size;
+my $blocks_per_dev = 0;
+my $devs_per_block = 0;
+my $ret = 0;
+
+sub chmem_usage()
+{
+	print <<HERE;
+Usage: $script_name [OPTIONS] SIZE|RANGE
+
+The $script_name command sets a particular size or range of memory online
+or offline.
+
+Specify SIZE as <size>[m|M|g|G]. With m or M, <size> specifies the memory
+size in MB (1024 x 1024 bytes). With g or G, <size> specifies the memory size
+in GB (1024 x 1024 x 1024 bytes). The default unit is MB.
+
+Specify RANGE in the form 0x<start>-0x<end> as shown in the output of the
+lsmem command. <start> is the hexadecimal address of the first byte and <end>
+is the hexadecimal address of the last byte in the memory range.
+
+SIZE and RANGE must be aligned to the Linux memory block size, as shown in
+the output of the lsmem command.
+
+OPTIONS
+    -e, --enable
+        Set the given RANGE or SIZE of memory online.
+
+    -d, --disable
+        Set the given RANGE or SIZE of memory offline.
+
+    -h, --help
+        Print a short help text, then exit.
+
+    -v, --version
+        Print the version number, then exit.
+HERE
+}
+
+sub chmem_version()
+{
+	print "$script_name: version %S390_TOOLS_VERSION%\n";
+	print "Copyright IBM Corp. 2010\n";
+}
+
+sub chmem_get_dev_size()
+{
+	my $i = 0;
+	my $device = 0;
+	my $old_device = 0;
+
+	while (-d "$memdir/memory$i") {
+		$device = `cat $memdir/memory$i/phys_device`;
+		chomp($device);
+		if ($device > $old_device) {
+			$dev_size = int($dev_size / ($device - $old_device));
+			last;
+		}
+		$dev_size += $block_size;
+		$i++;
+	}
+}
+
+sub chmem_online($)
+{
+	my $block = shift;
+
+	qx(echo online > $memdir/memory$block/state 2>/dev/null);
+	return $? >> 8;
+}
+
+sub chmem_offline($)
+{
+	my $block = shift;
+
+	qx(echo offline > $memdir/memory$block/state 2>/dev/null);
+	return $? >> 8;;
+}
+
+sub chmem_read_attr($$$)
+# parameters: state, device, block
+{
+	my @attributes = qw(state phys_device);
+	foreach (0..1) {
+		$_[$_] = `cat $memdir/memory$_[2]/$attributes[$_]`;
+		chomp($_[$_]);
+	}
+}
+
+sub chmem_read_devices()
+{
+	my $i = 0;
+	my $device = 0;
+	my $old_device = 0;
+	my $blocks = 0;
+	my $state;
+
+	while (-d "$memdir/memory$i") {
+		chmem_read_attr($state, $device, $i);
+		if ($device != $old_device) {
+			$devices->{$old_device}->{'id'} = $old_device;
+			$devices->{$old_device}->{'blocks'} = $blocks;
+			$old_device = $device;
+			$blocks = 0;
+		}
+		if ($state eq "online") {
+			$blocks++;
+		}
+		$i++;
+	}
+	$devices->{$old_device}->{'blocks'} = $blocks;
+	$devices->{$old_device}->{'id'} = $old_device;
+}
+
+sub chmem_dev_action($$)
+{
+	my ($dev_id, $blocks) = @_;
+	my ($start_block, $end_block, $tmp_block, $max_blocks);
+	my $state;
+	my $i = 0;
+	my $count = 0;
+
+	if ($blocks_per_dev > 0) {
+		$start_block = $dev_id * $blocks_per_dev;
+		$end_block = $start_block + $blocks_per_dev - 1;
+		$max_blocks = $blocks_per_dev;
+	} else {
+		$start_block = int($dev_id / $devs_per_block);
+		$end_block = $start_block;
+		$max_blocks = 1;
+	}
+	if ($blocks > $max_blocks) {
+		$blocks = $max_blocks;
+	}
+	while ($count < $blocks && $i < $max_blocks) {
+		$tmp_block = $online ? $start_block + $i : $end_block - $i;
+		$state = `cat $memdir/memory$tmp_block/state`;
+		chomp($state);
+		if ($offline && $state eq "online") {
+			$count++ unless chmem_offline($tmp_block);
+		}
+		if ($online && $state eq "offline") {
+			$count++ unless chmem_online($tmp_block);
+		}
+		$i++;
+	}
+	return $count;
+}
+
+sub chmem_size($)
+{
+	my $size = shift;
+	my ($blocks, $dev_blocks, $dev_id);
+
+	$blocks = int($size / $block_size);
+	if ($online) {
+		foreach my $device (sort {$b->{'blocks'} <=> $a->{'blocks'} ||
+					  $a->{'id'} <=> $b->{'id'}}
+				    values %{$devices}) {
+			$dev_blocks = $device->{'blocks'};
+			$dev_id = $device->{'id'};
+			if ($dev_blocks < $blocks_per_dev || $dev_blocks == 0) {
+				$blocks -= chmem_dev_action($dev_id, $blocks);
+				if ($blocks == 0) {
+					last;
+				}
+			}
+		}
+		if ($blocks > 0) {
+			printf(STDERR "chmem: Could only set %lu MB of memory ".
+			       "online.\n", $size - $blocks * $block_size);
+			$ret = 1;
+		}
+	} else {
+		foreach my $device (sort {$a->{'blocks'} <=> $b->{'blocks'} ||
+					  $b->{'id'} <=> $a->{'id'}}
+				    values %{$devices}) {
+			$dev_blocks = $device->{'blocks'};
+			$dev_id = $device->{'id'};
+			if ($dev_blocks > 0) {
+				$blocks -= chmem_dev_action($dev_id, $blocks);
+				if ($blocks == 0) {
+					last;
+				}
+			}
+		}
+		if ($blocks > 0) {
+			printf(STDERR "chmem: Could only set %lu MB of memory ".
+			       "offline.\n", $size - $blocks * $block_size);
+			$ret = 1;
+		}
+	}
+}
+
+sub chmem_range($$)
+{
+	my ($start, $end) = @_;
+	my $block = 0;
+	my $state;
+
+	while ($start < $end && $block < $total_blocks - 1) {
+		$block = int($start / ($block_size << 20));
+		$state = `cat $memdir/memory$block/state`;
+		chomp($state);
+		if ($online && $state eq "offline") {
+			if (chmem_online($block)) {
+				printf(STDERR "chmem: Could not set ".
+				       "0x%016x-0x%016x online\n", $start,
+				       $start + ($block_size << 20) - 1);
+				$ret = 1;
+			}
+		}
+		if ($offline && $state eq "online") {
+			if (chmem_offline($block)) {
+				printf(STDERR "chmem: Could not set ".
+				       "0x%016x-0x%016x offline\n", $start,
+				       $start + ($block_size << 20) - 1);
+				$ret = 1;
+			}
+		}
+		$start += $block_size << 20;
+	}
+}
+
+sub chmem_check()
+{
+	unless (-d $memdir) {
+		die "chmem: No memory hotplug interface in sysfs ($memdir).\n";
+	}
+	$block_size = `cat $memdir/block_size_bytes`;
+	chomp($block_size);
+	if ($block_size =~ /(?:0x)?([[:xdigit:]]+)/) {
+		$block_size = unpack("Q", pack("H16",
+				     substr("0" x 16 . $1, -16)));
+		$block_size = $block_size >> 20;
+	} else {
+		die "chmem: Unknown block size format in sysfs.\n";
+	}
+	if ($online == 0 && $offline == 0) {
+		die "chmem: Please specify one of the options -e or -d.\n";
+	}
+	if ($online == 1 && $offline == 1) {
+		die "chmem: You cannot specify both options -e and -d.\n";
+	}
+
+	while (-d "$memdir/memory$total_blocks") {
+		$total_blocks++;
+	}
+	chmem_get_dev_size();
+	if ($dev_size >= $block_size) {
+		$blocks_per_dev = int($dev_size / $block_size);
+	} else {
+		$devs_per_block = int($block_size / $dev_size);
+	}
+}
+
+sub chmem_action()
+{
+	my ($start, $end, $size, $unit);
+
+	if (!defined($ARGV[0])) {
+		die "chmem: Missing size or range.\n";
+	}
+	if ($ARGV[0] =~ /^0x([[:xdigit:]]+)-0x([[:xdigit:]]+)$/) {
+		$start = unpack("Q", pack("H16", substr("0" x 16 . $1, -16)));
+		$end = unpack("Q", pack("H16", substr("0" x 16 . $2, -16)));
+		if ($start % ($block_size << 20) ||
+		    ($end + 1) % ($block_size << 20)) {
+			die "chmem: Start address and (end address + 1) must ".
+			    "be aligned to memory block size ($block_size MB).\n";
+		}
+		chmem_range($start, $end);
+	} else {
+		if ($ARGV[0] =~ m/^(\d+)([mg]?)$/i) {
+			$size = $1;
+			$unit = $2 || "";
+			if ($unit =~ /g/i) {
+				$size = $size << 10;
+			}
+			if ($size % $block_size) {
+				die "chmem: Size must be aligned to memory ".
+				    "block size ($block_size MB).\n";
+			}
+			chmem_size($size);
+		} else {
+			printf(STDERR "chmem: Invalid size or range: %s\n",
+			       $ARGV[0]);
+			exit 1;
+		}
+	}
+}
+
+
+# Main
+unless (GetOptions('v|version' => sub {chmem_version(); exit 0;},
+		  'h|help'    => sub {chmem_usage(); exit 0;},
+		  'e|enable'  => \$online,
+		  'd|disable' => \$offline)) {
+	die "Try '$script_name --help' for more information.\n";
+};
+
+chmem_read_devices();
+chmem_check();
+chmem_action();
+exit $ret;
--- /dev/null
+++ b/zconf/chmem.8
@@ -0,0 +1,71 @@
+.TH CHMEM 8 "Apr 2010" "s390-tools"
+.
+.
+.SH NAME
+chmem \- set memory online or offline.
+.
+.SH SYNOPSIS
+.B chmem
+.RB OPTIONS
+.RB [SIZE|RANGE]
+.
+.
+.SH DESCRIPTION
+The chmem command sets a particular size or range of memory online or offline.
+.
+.IP "\(hy" 2
+Specify SIZE as <size>[m|M|g|G]. With m or M, <size> specifies the memory
+size in MB (1024 x 1024 bytes). With g or G, <size> specifies the memory size
+in GB (1024 x 1024 x 1024 bytes). The default unit is MB.
+.
+.IP "\(hy" 2
+Specify RANGE in the form 0x<start>-0x<end> as shown in the output of the
+lsmem command. <start> is the hexadecimal address of the first byte and <end>
+is the hexadecimal address of the last byte in the memory range.
+.
+.PP
+SIZE and RANGE must be aligned to the Linux memory block size, as shown in
+the output of the lsmem command.
+
+Setting memory online can fail if the hypervisor does not have enough memory
+left, for example because memory was overcommitted. Setting memory offline can
+fail if Linux cannot free the memory. If only part of the requested memory can
+be set online or offline, a message tells you how much memory was set online
+or offline instead of the requested amount.
+.
+.
+.SH OPTIONS
+.TP
+.BR \-h ", " \-\-help
+Print a short help text, then exit.
+.
+.TP
+.BR \-v ", " \-\-version
+Print the version number, then exit.
+.
+.TP
+.BR \-e ", " \-\-enable
+Set the given RANGE or SIZE of memory online.
+.
+.TP
+.BR \-d ", " \-\-disable
+Set the given RANGE or SIZE of memory offline.
+.
+.
+.SH EXAMPLES
+.TP
+.B chmem --enable 1024
+This command requests 1024 MB of memory to be set online.
+.
+.TP
+.B chmem -e 2g
+This command requests 2 GB of memory to be set online.
+.
+.TP
+.B chmem --disable 0x00000000e4000000-0x00000000f3ffffff
+This command requests the memory range starting with 0x00000000e4000000
+and ending with 0x00000000f3ffffff to be set offline.
+.
+.
+.SH SEE ALSO
+.BR lsmem (8)
--- /dev/null
+++ b/zconf/lsmem
@@ -0,0 +1,158 @@
+#!/usr/bin/perl
+###############################################################################
+# lsmem - script to show memory hotplug status.
+#
+# Copyright IBM Corp. 2010
+# Author(s): Gerald Schaefer <gerald.schaefer@de.ibm.com>
+###############################################################################
+
+use strict;
+use warnings;
+use Getopt::Long qw(:config no_ignore_case no_auto_abbrev);
+use File::Basename;
+
+my $script_name = fileparse($0);
+my $memdir = "/sys/devices/system/memory";
+my $block_size = 0;
+my $list_all = 0;
+my $dev_size = 0;
+
+
+sub lsmem_read_attr($$$$)
+# parameters: state, rem, device, block_nr
+{
+	my @attributes = qw(state removable phys_device);
+	foreach (0..2) {
+		$_[$_] = `cat $memdir/memory$_[3]/$attributes[$_]`;
+		chomp($_[$_]);
+	}
+}
+
+sub lsmem_get_dev_size()
+{
+	my $i = 0;
+	my ($device, $old_device) = (0, 0);
+
+	while (-d "$memdir/memory$i") {
+		$device = `cat $memdir/memory$i/phys_device`;
+		chomp($device);
+		if ($device > $old_device) {
+			$dev_size = int($dev_size / ($device - $old_device));
+			last;
+		}
+		$dev_size += $block_size;
+		$i++;
+	}
+}
+
+sub lsmem_list()
+{
+	my $i = 0;
+	my ($start, $end, $size) = (0, 0, 0);
+	my ($state, $old_state) = (0, 0);
+	my ($rem, $old_rem) = (0, 0);
+	my ($device, $old_device) = (0, 0);
+	my ($mem_online, $mem_offline) = (0, 0);
+	my ($last_block, $end_dev) = (0, 0);
+
+	if (-d "$memdir/memory0") {
+		lsmem_read_attr($old_state, $old_rem, $old_device, 0);
+	} else {
+		die "lsmem: No memory hotplug interface in sysfs ($memdir).\n";
+	}
+
+	$block_size = `cat $memdir/block_size_bytes`;
+	chomp($block_size);
+	if ($block_size =~ /(?:0x)?([[:xdigit:]]+)/) {
+		$block_size = unpack("Q", pack("H16",
+				     substr("0" x 16 . $1, -16)));
+		$block_size = $block_size >> 20;
+	} else {
+		die "lsmem: Unknown block size format in sysfs.\n";
+	}
+	lsmem_get_dev_size();
+
+	print <<HERE;
+Address Range                          Size (MB)  State    Removable  Device
+===============================================================================
+HERE
+	while (-d "$memdir/memory$i") {
+		$i++;
+		if (-d "$memdir/memory$i") {
+			lsmem_read_attr($state, $rem, $device, $i);
+		} else {
+			$last_block = 1;
+		}
+		if ($state ne $old_state || $rem != $old_rem || $list_all ||
+		    $last_block) {
+			$end = $i * ($block_size << 20) - 1;
+			$size = ($end - $start + 1) >> 20;
+			if ($old_state eq "going-offline") {
+				$old_state = "on->off";
+			}
+			printf("0x%016x-0x%016x %10lu  %-7s ", $start, $end,
+				$size, $old_state);
+			if ($old_state eq "online") {
+				printf(" %-9s  ", $old_rem ? "yes" : "no");
+				$mem_online += $size;
+			} else {
+				printf(" %-9s  ", "-");
+				$mem_offline += $size;
+			}
+			$end_dev = ($end / $dev_size) >> 20;
+			if ($old_device == $end_dev) {
+				printf("%d\n", $old_device);
+			} else {
+				printf("%d-%d\n", $old_device, $end_dev);
+			}
+			$old_state = $state;
+			$old_rem = $rem;
+			$old_device = $device;
+			$start = $end + 1;
+		}
+	}
+	printf("\n");
+	printf("Memory device size  : %lu MB\n", $dev_size);
+	printf("Memory block size   : %lu MB\n", $block_size);
+	printf("Total online memory : %lu MB\n", $mem_online);
+	printf("Total offline memory: %lu MB\n", $mem_offline);
+}
+
+sub lsmem_usage()
+{
+	print <<HERE;
+Usage: $script_name [OPTIONS]
+
+The $script_name command lists the ranges of available memory with their online
+status. The listed memory blocks correspond to the memory block representation
+in sysfs. The command also shows the memory block size, the device size, and
+the amount of memory in online and offline state.
+
+OPTIONS
+    -a, --all
+       List each individual memory block, instead of combining memory blocks
+       with similar attributes.
+
+    -h, --help
+       Print a short help text, then exit.
+
+    -v, --version
+       Print the version number, then exit.
+HERE
+}
+
+sub lsmem_version()
+{
+	print "$script_name: version %S390_TOOLS_VERSION%\n";
+	print "Copyright IBM Corp. 2010\n";
+}
+
+
+# Main
+unless (GetOptions('v|version' => sub {lsmem_version(); exit 0;},
+		   'h|help'    => sub {lsmem_usage(); exit 0;},
+		   'a|all'     => \$list_all)) {
+	die "Try '$script_name --help' for more information.\n";
+};
+
+lsmem_list();
--- /dev/null
+++ b/zconf/lsmem.8
@@ -0,0 +1,69 @@
+.TH LSMEM 8 "Apr 2010" s390\-tools
+.
+.
+.SH NAME
+lsmem \- list the ranges of available memory with their online status.
+.
+.
+.SH SYNOPSIS
+.B lsmem
+.RB [OPTIONS]
+.
+.
+.SH DESCRIPTION
+The lsmem command lists the ranges of available memory with their online
+status. The listed memory blocks correspond to the memory block representation
+in sysfs. The command also shows the memory block size, the device size, and
+the amount of memory in online and offline state.
+.
+.SS "Column description"
+.
+.TP 4
+Address Range
+Start and end address of the memory range.
+.
+.TP 4
+Size
+Size of the memory range in MB (1024 x 1024 bytes).
+.
+.TP 4
+State
+Indication of the online status of the memory range. State on->off means
+that the address range is in transition from online to offline.
+.
+.TP 4
+Removable
+"yes" if the memory range can be set offline, "no" if it cannot be set offline.
+A dash ("\-") means that the range is already offline.
+.
+.TP 4
+Device
+Device number or numbers that correspond to the memory range.
+
+Each device represents a memory unit for the hypervisor in control of the
+memory. The hypervisor cannot reuse a memory unit unless the corresponding
+memory range is completely offline. For best memory utilization, each device
+should either be completely online or completely offline.
+
+The chmem command with the size parameter automatically chooses the best suited
+device or devices when setting memory online or offline. The device size depends
+on the hypervisor and on the amount of total online and offline memory.
+.
+.
+.SH OPTIONS
+.TP
+.BR \-a ", " \-\-all
+List each individual memory block, instead of combining memory blocks with
+similar attributes.
+.
+.TP
+.BR \-h ", " \-\-help
+Print a short help text, then exit.
+.
+.TP
+.BR \-v ", " \-\-version
+Print the version number, then exit.
+.
+.
+.SH SEE ALSO
+.BR chmem (8)
