/* Copyright (C) 2009 Intel Corporation 
   Author: Andi Kleen
   Common Intel CPU code.

   mcelog is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License as published by the Free Software Foundation; version
   2.

   mcelog is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should find a copy of v2 of the GNU General Public License somewhere
   on your Linux system; if not, write to the Free Software Foundation, 
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include <stddef.h>
#include "mcelog.h"
#include "intel.h"
#include "bitfield.h"
#include "nehalem.h"
#include "memdb.h"
#include "page.h"
#include "sandy-bridge.h"
#include "ivy-bridge.h"
#include "haswell.h"
#include "skylake_xeon.h"
#include "i10nm.h"
#include "sapphire.h"

int memory_error_support;

void intel_cpu_init(enum cputype cpu)
{
	if (cpu == CPU_ATOM || cpu == CPU_CORE2 || cpu == CPU_DUNNINGTON ||
	    cpu == CPU_GENERIC || cpu == CPU_K8 || cpu == CPU_P4 ||
	    cpu == CPU_P6OLD || cpu == CPU_TULSA)
		memory_error_support = 0;
	else
		memory_error_support = 1;
}

enum cputype select_intel_cputype(int family, int model)
{
	int ret;

	if (family == 15) { 
		if (model == 6) 
			return CPU_TULSA;
		return CPU_P4;
	} 
	if (family == 6) { 
		if (model >= 0x1a && model != 28) 
			memory_error_support = 1;

		ret = lookup_intel_cputype(model);
		if (ret != -1)
			return ret;
		if (model > 0x1a) {
			Eprintf("Family 6 Model %u CPU: only decoding architectural errors\n",
				model);
			return CPU_INTEL; 
		}
	}
	if (family > 6) { 
		Eprintf("Family %u Model %u CPU: only decoding architectural errors\n",
				family, model);
		return CPU_INTEL;
	}
	Eprintf("Unknown Intel CPU type family %u model %u\n", family, model);
	return family == 6 ? CPU_P6OLD : CPU_GENERIC;
}

int is_intel_cpu(int cpu)
{
	return cpu >= CPU_INTEL;
}

static int intel_memory_error(struct mce *m, unsigned recordlen)
{
	u32 mca = m->status & 0xffff;
	if ((mca >> 7) == 1) { 
		unsigned corr_err_cnt = 0;
		int channel[2] = { (mca & 0xf) == 0xf ? -1 : (int)(mca & 0xf), -1 };
		int dimm[2] = { -1, -1 };

		switch (cputype) { 
		case CPU_NEHALEM:
			nehalem_memerr_misc(m, channel, dimm);
			break;
		case CPU_SANDY_BRIDGE_EP:
			sandy_bridge_ep_memerr_misc(m, channel, dimm);
			break;
		case CPU_IVY_BRIDGE_EPEX:
			ivy_bridge_ep_memerr_misc(m, channel, dimm);
			break;
		case CPU_HASWELL_EPEX:
		case CPU_BROADWELL_EPEX:
			haswell_memerr_misc(m, channel, dimm);
			break;
		case CPU_SKYLAKE_XEON:
			skylake_memerr_misc(m, channel, dimm);
			break;
		case CPU_ICELAKE_XEON:
		case CPU_ICELAKE_DE:
		case CPU_TREMONT_D:
			i10nm_memerr_misc(m, channel, dimm);
			break;
		case CPU_SAPPHIRERAPIDS:
		case CPU_EMERALDRAPIDS:
			sapphire_memerr_misc(m, channel, dimm);
			break;
		default:
			break;
		} 

		if (recordlen > offsetof(struct mce, mcgcap) && m->mcgcap & MCG_CMCI_P)
 			corr_err_cnt = EXTRACT(m->status, 38, 52);
		memory_error(m, channel[0], dimm[0], corr_err_cnt, recordlen);
		account_page_error(m, channel[0], dimm[0]);

		/* 
		 * When both DIMMs have a error account the error twice to the page.
		 */
		if (channel[1] != -1) {
			memory_error(m, channel[1], dimm[1], corr_err_cnt, recordlen);
			account_page_error(m, channel[1], dimm[1]);
		}

		return 1;
	}

	return 0;
}

/* No bugs known, but filter out memory errors if the user asked for it */
int mce_filter_intel(struct mce *m, unsigned recordlen)
{
	if (intel_memory_error(m, recordlen) == 1) 
		return !filter_memory_errors;
	return 1;
}
