#!/usr/bin/perl
###############################################################################
# lsmem - script to show memory hotplug status.
#
# Copyright IBM Corp. 2010, 2011
# 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;
my @entries;


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 ($device, $old_device, $block, $old_block) = (0, 0, 0, 0);

	foreach (@entries) {
		$_ =~ /memory(\d+)/;
		$block = $1;
		$device = `cat $_/phys_device`;
		chomp($device);
		if ($device > $old_device) {
			$dev_size = int((($block - $old_block) * $block_size) /
				    ($device - $old_device));
			last;
		}
		$dev_size += $block_size;
		$old_block = $block;
	}
}

sub lsmem_list()
{
	my $block = 0;
	my ($start, $end, $size) = (0, 0, 0);
	my ($state, $next_state) = (0, 0);
	my ($rem, $next_rem) = (0, 0);
	my ($device, $next_device, $end_dev) = (0, 0, 0);
	my ($mem_online, $mem_offline) = (0, 0);
	my $mem_hole;

	$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();
	# Start with $mem_hole = 1 to initialize $start, $state, $rem, $device
	$mem_hole = 1;

	print <<HERE;
Address Range                          Size (MB)  State    Removable  Device
===============================================================================
HERE
	foreach (@entries) {
		$_ =~ /memory(\d+)/;
		$block = $1;
		if ($mem_hole) {
			lsmem_read_attr($state, $rem, $device, $block);
			$start = ($block) * ($block_size << 20);
			$mem_hole = 0;
		}
		# check next block or memory hole
		$block++;
		if (-d "$memdir/memory".$block) {
			lsmem_read_attr($next_state, $next_rem, $next_device,
					$block);
		} else {
			$mem_hole = 1
		}
		if ($state ne $next_state || $rem != $next_rem || $list_all ||
		    $mem_hole) {
			$end = ($block) * ($block_size << 20) - 1;
			$size = ($end - $start + 1) >> 20;
			if ($state eq "going-offline") {
				$state = "on->off";
			}
			printf("0x%016x-0x%016x %10lu  %-7s ", $start, $end,
				$size, $state);
			if ($state eq "online") {
				printf(" %-9s  ", $rem ? "yes" : "no");
				$mem_online += $size;
			} else {
				printf(" %-9s  ", "-");
				$mem_offline += $size;
			}
			$end_dev = ($end / $dev_size) >> 20;
			if ($device == $end_dev) {
				printf("%d\n", $device);
			} else {
				printf("%d-%d\n", $device, $end_dev);
			}
			$state = $next_state;
			$rem = $next_rem;
			$device = $end_dev + 1;
			$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, 2011\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";
};

@entries = (sort {length($a) <=> length($b) || $a cmp $b} <$memdir/memory*>);
if (@entries == 0) {
	die "lsmem: No memory hotplug interface in sysfs ($memdir).\n";
}
lsmem_list();
