* Copyright 2010-2017 Intel Corporation.
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2,
* as published by the Free Software Foundation.
* This program 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.
* Disclaimer: The codes contained in these modules may be specific to
* the Intel Software Development Platform codenamed Knights Ferry,
* and the Intel product codenamed Knights Corner, and are not backward
* compatible with other Intel products. Additionally, Intel will NOT
* support the codes or instruction set in future products.
* Intel offers no warranty of any kind regarding the code. This code is
* licensed on an "AS IS" basis and Intel is not obligated to provide
* any support, assistance, installation, training, or other services
* of any kind. Intel is also not obligated to provide any updates,
* enhancements or extensions. Intel specifically disclaims any warranty
* of merchantability, non-infringement, fitness for any particular
* purpose, and any other warranty.
* Further, Intel disclaims all liability of any kind, including but
* not limited to liability for infringement of any proprietary rights,
* relating to the use of the code, even if Intel is notified of the
* possibility of such liability. Except as expressly stated in an Intel
* license agreement provided with this code and agreed upon with Intel,
* no license, express or implied, by estoppel or otherwise, to any
* intellectual property rights is granted herein.
* Contains code to handle module install/deinstall
* and handling proper registration(s) to SCIF, sysfs
* pseudo file system, timer ticks, I2C driver and
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/proc_fs.h>
#include <linux/workqueue.h>
#include <linux/kthread.h>
#include <linux/bitmap.h>
#include <linux/cpumask.h>
#include <asm/mic/mic_common.h>
#include <asm/mic/mic_knc/autobaseaddress.h>
#include <asm/mic/mic_knc/micsboxdefine.h>
#if MT_VERBOSE || MC_VERBOSE || PM_VERBOSE
* For making scif_epd_t non-opague
#define _MIC_MICBASEDEFINE_REGISTERS_H_ 1
** Lookup table to map API opcode into MT function.
** As we have to deal with both KnF and KnC, functions to
** retrieve information may be generic, in micras_common.c,
** or platform specific, in micras_kn{cf}.c.
** Code location is transparent to this table.
** Some MT functions can safely be called without
** serialization, e.g. if they are read-only or use
** atomics to get/set variables. The 'simple' flag tells
** which functions are safe to call without serialization.
** Other functions should be called thru micras_mt_call().
** See micras_api.h and micpm_api.h for function details.
static struct fnc_tab fnc_map
[] = {
{ MR_REQ_HWINF
, 1, 0, mr_get_hwinf
},
{ MR_REQ_VERS
, 1, 0, mr_get_vers
},
{ MR_REQ_CFREQ
, 0, 0, mr_get_freq
},
{ MR_SET_CFREQ
, 0, 1, mr_set_freq
},
{ MR_REQ_CVOLT
, 0, 0, mr_get_volt
},
{ MR_SET_CVOLT
, 0, 1, mr_set_volt
},
{ MR_REQ_PWR
, 0, 0, mr_get_power
},
{ MR_REQ_PLIM
, 0, 0, mr_get_plim
},
{ MR_SET_PLIM
, 0, 1, mr_set_plim
},
{ MR_REQ_CLST
, 0, 0, mr_get_clst
},
{ MR_ENB_CORE
, 0, 1, 0 },
{ MR_DIS_CORE
, 0, 1, 0 },
{ MR_REQ_GDDR
, 1, 0, mr_get_gddr
},
{ MR_REQ_GFREQ
, 1, 0, mr_get_gfreq
},
{ MR_SET_GFREQ
, 1, 1, 0 },
{ MR_REQ_GVOLT
, 1, 0, mr_get_gvolt
},
{ MR_SET_GVOLT
, 1, 1, 0 },
{ MR_REQ_TEMP
, 0, 0, mr_get_temp
},
{ MR_REQ_FAN
, 0, 0, mr_get_fan
},
{ MR_SET_FAN
, 0, 1, mr_set_fan
},
{ MR_REQ_ECC
, 1, 0, mr_get_ecc
},
{ MR_REQ_TRC
, 1, 0, mr_get_trc
},
{ MR_SET_TRC
, 1, 1, mr_set_trc
},
{ MR_REQ_TRBO
, 0, 0, mr_get_trbo
},
{ MR_SET_TRBO
, 0, 1, mr_set_trbo
},
{ MR_REQ_OCLK
, 0, 0, 0 },
{ MR_SET_OCLK
, 0, 1, 0 },
{ MR_REQ_CUTL
, 0, 0, mr_get_cutl
},
{ MR_REQ_MEM
, 0, 0, mr_get_mem
},
{ MR_REQ_OS
, 0, 0, mr_get_os
},
{ MR_REQ_PROC
, 0, 0, mr_get_proc
},
{ MR_REQ_THRD
, 0, 0, 0 },
{ MR_REQ_PVER
, 1, 0, mr_get_pver
},
{ MR_CMD_PKILL
, 0, 1, mr_cmd_pkill
},
{ MR_CMD_UKILL
, 0, 1, mr_cmd_ukill
},
#if defined(CONFIG_MK1OM)
{ MR_GET_SMC
, 0, 0, mr_get_smc
},
{ MR_SET_SMC
, 0, 0, mr_set_smc
},
#if defined(CONFIG_ML1OM) && USE_FSC
{ MR_GET_SMC
, 0, 0, mr_get_fsc
},
{ MR_SET_SMC
, 0, 1, mr_set_fsc
},
{ MR_REQ_PMCFG
, 0, 0, mr_get_pmcfg
},
#if defined(CONFIG_MK1OM)
{ MR_REQ_LED
, 0, 0, mr_get_led
},
{ MR_SET_LED
, 0, 1, mr_set_led
},
{ MR_REQ_PROCHOT
, 0, 0, mr_get_prochot
},
{ MR_SET_PROCHOT
, 0, 1, mr_set_prochot
},
{ MR_REQ_PWRALT
, 0, 0, mr_get_pwralt
},
{ MR_SET_PWRALT
, 0, 1, mr_set_pwralt
},
{ MR_REQ_PERST
, 0, 0, mr_get_perst
},
{ MR_SET_PERST
, 0, 1, mr_set_perst
},
{ MR_REQ_TTL
, 0, 0, mr_get_ttl
},
#if defined(CONFIG_MK1OM) && USE_PM
{ PM_REQ_PL0
, 1, 0, pm_get_pl0
},
{ PM_SET_PL0
, 1, 1, pm_set_pl0
},
{ PM_REQ_PL1
, 1, 0, pm_get_pl1
},
{ PM_SET_PL1
, 1, 1, pm_set_pl1
},
{ PM_REQ_PAVG
, 1, 0, pm_get_pavg
},
{ PM_REQ_PTTL
, 1, 0, pm_get_pttl
},
{ PM_REQ_VOLT
, 1, 0, pm_get_volt
},
{ PM_REQ_TEMP
, 1, 0, pm_get_temp
},
{ PM_REQ_TACH
, 1, 0, pm_get_tach
},
{ PM_REQ_TTTL
, 1, 0, pm_get_tttl
},
{ PM_REQ_FTTL
, 1, 0, pm_get_fttl
},
{ PM_SET_FTTL
, 1, 1, pm_set_fttl
},
** The monitoring thread.
** In fact this is a work_queue, that receive work items
** from several independent parties, such as SCIF, sysfs,
** out of band telemetry, PM and possibly timers.
** These parties pass a structure with information necessary
** for the call-out function called by the MT thread to operate.
** These structures must include the work item structure, such
** that the container_of() mechanism can be used to locate it.
** The MT thread does not by itself provide any feed-back on
** when a task was executed nor the results from it. Therefore
** if a feedback is requred, then the callout needs to provide
** their own methods, such as the wait queue used by function
** micras_mt_data() below. Experiments has shown that it is not
** safe to place work item or the wait queue on a stack (no
** idea why, could be a bug).
static int micras_stop
; /* Module shutdown */
static struct delayed_work micras_wq_init
; /* Setup work item */
static struct delayed_work micras_wq_tick
; /* Timer tick token */
static struct workqueue_struct
* micras_wq
; /* Monitor thread */
int micras_priv
; /* Call-out privileged */
int req
; /* Request opcode */
int rtn
; /* Return value */
int priv
; /* Privileged */
void * ptr
; /* Response buffer */
int (* fnc
)(void *); /* Call out */
struct work_struct wrk
; /* Work item */
wait_queue_head_t wqh
; /* Wait queue header */
#if defined(CONFIG_MK1OM) && WA_4845465
* SMC die temp update job.
* As per HSD #4845465 we push the die temperature
* to the SMC instead of the usual reverse direction.
* This has to happen at around 50 mSec intervals, which should
* be possible with a work queue implementation. If that turns out
* not to be reliable enough we may need a more direct approach.
* During the experiment, we want to override the pushed temp.
#define DIE_PROC 1 /* Enable die temp override */
#define SMC_PERIOD 50 /* SMC update interval, mSec */
#define JITTER_STATS 1 /* Enable jitter measurements */
static struct delayed_work micras_wq_smc
; /* SMC update token */
static int smc_4845465
; /* SMC push capable */
static int die_override
; /* Temperature override */
micras_mt_smc(struct work_struct
*work
)
extern int mr_smc_wr(uint8_t, uint32_t *);
* Re-arm for a callback in about 1 second.
* There is no guarantee this will be more than approximate.
queue_delayed_work(micras_wq
, &micras_wq_smc
, msecs_to_jiffies(SMC_PERIOD
));
* Time the interval in order to get some
* measurement on what jitter to expect.
* Leave a log message once every minute.
static uint64_t d
, t1
, t2
, s
, hi
, lo
= ~0;
* Show jitter in buckets representing 5 mSec.
* The center (#20) represent +- 2.5 mSec from reference.
* It is assumed TSC running at 1.1 GHz here, if PM kicks
* in the mesurements may be way off because it manipulate
* the system clock and indirectly the jiffy counter.
* It is assumed TSC running at 1.1 GHz here.
static uint64_t buckets
[41];
err
= ((d
* 10) / 11) - (50 * 1000 * 1000);
if (err
< -(25 * 100 * 1000))
bkt
= 19 + (err
+ (25 * 100 * 1000)) / (5 * 1000 * 1000);
if (err
> (25 * 100 * 1000))
bkt
= 21 + (err
- (25 * 100 * 1000)) / (5 * 1000 * 1000);
if ((n
% ((10 * 1000)/SMC_PERIOD
)) == ((10 * 1000)/SMC_PERIOD
) - 1) {
for(bkt
= 0; bkt
< 41; bkt
++) {
printk(" | %lld |", buckets
[bkt
]);
printk(" %lld", buckets
[bkt
]);
if ((n
% ((60 * 1000)/SMC_PERIOD
)) == ((60 * 1000)/SMC_PERIOD
) - 1)
printk("smc_upd: %lld, min %lld, max %lld, avg %lld\n", n
, lo
, hi
, s
/ n
);
#endif /* JITTER_STATS */
* Send update to SMC to register 0x50.
* The value to push at the SMC must have following content
* Bits 9:0 Device Temperature
* -> THERMAL_STATUS_2 bits 19:10
* -> THERMAL_STATUS_2 bit 31
* Bits 20:11 Thermal Monitor Control value
* -> THERMAL_STATUS_2 bits 9:0
* Bits 30:21 Fan Thermal Control value
* -> MICROCONTROLLER_FAN_STATUS bits 17:8
ts2
= mr_sbox_rl(0, SBOX_THERMAL_STATUS_2
);
mfs
= mr_sbox_rl(0, SBOX_MICROCONTROLLER_FAN_STATUS
);
tmp
= GET_BITS(9, 0, die_override
);
tmp
= PUT_BITS(9, 0, GET_BITS(19, 10, ts2
));
tmp
|= PUT_BIT(10, GET_BIT(31, ts2
)) |
PUT_BITS(20, 11, GET_BITS(9, 0, ts2
)) |
PUT_BITS(30, 21, GET_BITS(17, 8, mfs
));
if (mr_smc_wr(0x50, &tmp
))
printk("smc_upd: %lld, tmp %d, SMC write failed\n", n
, tmp
);
* Test proc file to override die temperature push.
* A value of 0 means no override, any other value is
* pushed as if it was a 'device temperature'.
static struct proc_dir_entry
* die_pe
;
* On writes: scan input line for single number.
die_write(struct file
* file
, const char __user
* buff
, size_t len
, loff_t
* off
)
* Get input line into kernel space
buf
= kmalloc(PAGE_SIZE
, GFP_KERNEL
);
if (copy_from_user(buf
, buff
, len
)) {
* Read a number in strtoul format 0.
ull
= simple_strtoull(cp
, &ep
, 0);
if (ep
== cp
|| (*ep
!= '\0' && !isspace(*ep
))) {
printk("Invalid die temp given\n");
die_override
= GET_BITS(9, 0, ull
);
printk("Die temp override set to %d C\n", die_override
);
* Swallow any trailing junk up to next newline
* On reads: return string of current override temp.
die_read(struct file
* file
, char __user
* buff
, size_t count
, loff_t
*ppos
)
len
= snprintf(buf
, sizeof(buf
), "%d\n", die_override
);
return simple_read_from_buffer(buff
, count
, ppos
, buf
, len
);
static const struct file_operations proc_die_operations
= {
* This is for periodic updates from the SMC,
* which (with a little luck) can be avoided
* at the cost of I2C communications during
micras_mt_tick(struct work_struct
*work
)
* Re-arm for a callback in about 1 second.
* There is no guarantee this will be more than approximate.
queue_delayed_work(micras_wq
, &micras_wq_tick
, msecs_to_jiffies(MT_PERIOD
));
* Dump elog prints into the kernel log
*TBD: debug tool, time-shifts messages, remove eventually.
msg_id
= atomic_read(&ee_seen
);
msg_top
= atomic_read(&ee_msg
);
while(++msg_id
<= msg_top
) {
buf
= ee_buf
+ (msg_id
% EE_BUF_COUNT
) * EE_BUF_LINELEN
;
* Handle SCIF & sysfs show/store requests
* By convention we know that the work item is member of
* a larger struct, which can readily be found using the
* container_of mechanism.
* Otherwise this routine just calls the function stored
* in the larger struct's mt_data element, and on its
* return wake up whoever is waiting for it's completion.
micras_mt_data(struct work_struct
* work
)
wq
= container_of(work
, struct wq_task
, wrk
);
wq
->rtn
= wq
->fnc(wq
->ptr
);
* Helper to pass jobs (work items) to the monitoring thread.
* As input it receives details on function to be called, one
* argument to pass to that function, the opcode associated
* with the function and a function return value. The latter
* will be set to -MR_ERR_PEND, and we'll expect the callout
* The work item is the only piece of information passed to
* the work queue callout, so we'll wrap it into a larger
* structure along with the received details such that the
* work queue can perform a function call on our behalf.
micras_mt_tsk(struct wq_task
* wq
)
* Create a work item for the RAS thread,
* enqueue and wait for it's completion.
*TBD: Timeout length to be revisited
INIT_WORK_ONSTACK(&wq
->wrk
, micras_mt_data
);
init_waitqueue_head(&wq
->wqh
);
queue_work(micras_wq
, &wq
->wrk
);
err
= wait_event_interruptible_timeout(wq
->wqh
,
wq
->rtn
!= -MR_ERR_PEND
, msecs_to_jiffies(1000));
* Check for potential errors, which for now can only be
* "interrupted" or "timeout". In both cases try cancel the work
* item from MT thread. If cancel succeds (returns true) then
* the work item was still "pending" and is now removed from the
* work queue, i.e. it is safe to continue (with error).
* Otherwise, the cancel operation will wait for the work item's
* call-out function to finish, which kind of defies the purpose
* of "interruptable". However, we cannot leave until it is certain
* that it will not be accessed by the RAS thread.
if (err
== -ERESTARTSYS
|| err
== 0) {
printk("MT tsk: interrupted or failure, err %d\n", err
);
printk("MT tsk: FAILED: cmd %d, rtn %d, fnc %p, ptr %p\n",
wq
->req
, wq
->rtn
, wq
->fnc
, wq
->ptr
);
err
= cancel_work_sync(&wq
->wrk
);
printk("MT tsk: work canceled (%d)\n", err
);
* Completed, turn interrupts and timeouts into MR errors.
printk("MT tsk: cmd %d, err %d, time %llu\n", wq
->req
, err
, stop
- start
);
* Public interface to the MT functions
* Caller responsible for passing a buffer large enough
* to hold data for reads or writes (1 page will do,
* but structs matching the commands are recommended).
* Returned data are structs defined in micras.h
micras_mt_call(uint16_t cmd
, void * buf
)
if (fnc_map
[cmd
].simple
) {
* Fast access, just call function
err
= fnc_map
[cmd
].fnc(buf
);
* Slow access, go through serializer.
* We allocate a work queue task for the MT thread,
* stuff arguments in it, run task, and then free
wq
= kmalloc(sizeof(* wq
), GFP_KERNEL
);
printk("Scif: CP work task alloc failed\n");
memset(wq
, '\0', sizeof(*wq
));
wq
->fnc
= (int (*)(void *)) fnc_map
[cmd
].fnc
;
EXPORT_SYMBOL_GPL(micras_mt_call
);
** The sysfs nodes provided by this module is not really associated
** with a 'struct device', since we don't create device entries for
** access through '/dev'. Instead we register a 'struct class'
** with nodes defined with the CLASS_ATTR macro.
** Reasons for this choice are:
** - we don't want a device node created
** - we don't need (at least now) to create udev events
** - we don't act on suspend/resume transitions
** - we don't want to have our files unnecessarily deep
** in the sysfs file system.
** The sysfs layout is intended to look like:
** /sys/class/micras/ Root of this driver
** /clst Core information
** /cutl Core utilization
** /ecc Error correction mode
** /mem Memory utilization
** /temp Board tempearatures
** /vers uOS/Flash version
** The following should be removed as there are better tools
** available in /proc/<pid>/{stat|status|smap}, /proc/meminfo,
** /proc/stat, /proc/uptime, /proc/loadavg, and /proc/cpuinfo:
** Below we hand-craft a 'micras' class to sit under '/sys/class'
** with attribute nodes directly under it. Each attribute may
** have a 'show' and a 'store' handler, both called with a reference
** to its class (ras_class, may hold private data), it's class_attribute,
** a buffer reference, and for 'store's a string length. The buffer
** passed to 'show' is one page (PAGE_SIZE, 4096) which sets the
** upper limit on the return string(s). Return value of 'store'
** has to be either an error code (negative) or the count of bytes
** consumed. If consumed less than what's passed in, the store routine
** will be called again until all input data has been consumed.
** Function pointers are hardwired by the macros below since it
** is easy and simpler than using the fnc_map table. This may
** change if the command set expands uncontrolled.
** We have local helper funtions to handle array prints.
** Any locking required is handled in called routines, not here.
** Note: This is not coded for maximum performance, since the
** use of the MT thread to serialize access to card data
** has a cost of two task switches attached, both which
** may cause delays due to other system activity.
* Formatting routines for arrays of 16/32/64 bit unsigned ints.
* This reduces the printf argument list in _SHOW() macros below
* considerably, though perhaps at a cost in code efficiency.
* They need a scratch buffer in order to construct long lines.
* A quick swag at the largest possible response tells that we'll
* never exceed half if the page we are given to scribble into.
* So, instead of allocating print space, we'll simply use 2nd
* half of the page as scratch buffer.
#define BP (buf + (PAGE_SIZE/2)) /* Scratch pad location */
#define BL (PAGE_SIZE/2 - 1) /* Scratch size */
arr16(int16_t * arr
, int len
, char * buf
, int siz
)
for(n
= 0; n
< len
&& bs
< siz
; n
++)
bs
+= scnprintf(buf
+ bs
, siz
- bs
, "%s%u", n
? " " : "", arr
[n
]);
arr32(uint32_t * arr
, int len
, char * buf
, int siz
)
for(n
= 0; n
< len
&& bs
< siz
; n
++)
bs
+= scnprintf(buf
+ bs
, siz
- bs
, "%s%u", n
? " " : "", arr
[n
]);
arr64(uint64_t * arr
, int len
, char * buf
, int siz
)
for(n
= 0; n
< len
&& bs
< siz
; n
++)
bs
+= scnprintf(buf
+ bs
, siz
- bs
, "%s%llu", n
? " " : "", arr
[n
]);
#define _SHOW(op,rec,nam,str...) \
micras_show_##nam(struct class *class, \
struct class_attribute *attr, \
struct mr_rsp_##rec * r; \
wq = kmalloc(sizeof(* wq) + sizeof(* r), GFP_KERNEL); \
memset(wq, '\0', sizeof(* wq)); \
r = (struct mr_rsp_##rec *)(wq + 1); \
wq->fnc = (int (*)(void *)) mr_get_##nam; \
err = micras_mt_tsk(wq); \
len = scnprintf(buf, PAGE_SIZE, ##str); \
_SHOW(HWINF
, hwinf
, hwinf
, "%u %u %u %u %u %u "
"%c%c%c%c%c%c%c%c%c%c%c%c "
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
r
->rev
, r
->step
, r
->substep
, r
->board
, r
->fab
, r
->sku
,
r
->serial
[0], r
->serial
[1], r
->serial
[2], r
->serial
[3],
r
->serial
[4], r
->serial
[5], r
->serial
[6], r
->serial
[7],
r
->serial
[8], r
->serial
[9], r
->serial
[10], r
->serial
[11],
r
->guid
[0], r
->guid
[1], r
->guid
[2], r
->guid
[3],
r
->guid
[4], r
->guid
[5], r
->guid
[6], r
->guid
[7],
r
->guid
[8], r
->guid
[9], r
->guid
[10], r
->guid
[11],
r
->guid
[12], r
->guid
[13], r
->guid
[14], r
->guid
[15]);
_SHOW(VERS
, vers
, vers
, "\"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n",
r
->fboot0
+1, r
->fboot1
+1, r
->flash
[0] +1,
r
->flash
[1] +1, r
->flash
[2] +1, r
->fsc
+1, r
->uos
+1)
_SHOW(CFREQ
, freq
, freq
, "%u %u %s\n",
r
->cur
, r
->def
, arr32(r
->supt
, r
->slen
, BP
, BL
))
_SHOW(CVOLT
, volt
, volt
, "%u %u %s\n",
r
->cur
, r
->set
, arr32(r
->supt
, r
->slen
, BP
, BL
))
#if defined(CONFIG_MK1OM) || (defined(CONFIG_ML1OM) && USE_FSC)
_SHOW(PWR
, power
, power
, "%d\n%d\n%d\n%d\n%d\n%d\n%d\n%s\n%s\n%s\n",
arr32(&r
->vccp
.pwr
, 3, BP
, 32),
arr32(&r
->vddg
.pwr
, 3, BP
+ 32, 32),
arr32(&r
->vddq
.pwr
, 3, BP
+ 64, 32))
_SHOW(PLIM
, plim
, plim
, "%u %u %u\n",
r
->phys
, r
->hmrk
, r
->lmrk
)
_SHOW(CLST
, clst
, clst
, "%u %u\n",
_SHOW(GDDR
, gddr
, gddr
, "\"%s\" %u %u %u\n",
r
->dev
+1, r
->rev
, r
->size
, r
->speed
)
_SHOW(GFREQ
, gfreq
, gfreq
, "%u %u\n",
_SHOW(GVOLT
, gvolt
, gvolt
, "%u %u\n",
_SHOW(TEMP
, temp
, temp
, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
arr16(&r
->die
.cur
, 2, BP
, 32),
arr16(&r
->brd
.cur
, 2, BP
+ 32, 32),
arr16(&r
->fin
.cur
, 2, BP
+ 64, 32),
arr16(&r
->fout
.cur
, 2, BP
+ 96, 32),
arr16(&r
->gddr
.cur
, 2, BP
+ 128, 32),
arr16(&r
->vccp
.cur
, 2, BP
+ 160, 32),
arr16(&r
->vddg
.cur
, 2, BP
+ 224, 32),
arr16(&r
->vddq
.cur
, 2, BP
+ 256, 32))
_SHOW(FAN
, fan
, fan
, "%u %u %u\n",
r
->override
, r
->pwm
, r
->rpm
)
_SHOW(ECC
, ecc
, ecc
, "%d\n",
_SHOW(TRC
, trc
, trc
, "%d\n",
_SHOW(TRBO
, trbo
, trbo
, "%d %d %d\n",
r
->set
, r
->state
, r
->avail
)
_SHOW(LED
, led
, led
, "%d\n",
_SHOW(PROCHOT
, ptrig
, prochot
, "%d %d\n",
_SHOW(PWRALT
, ptrig
, pwralt
, "%d %d\n",
_SHOW(PERST
, perst
, perst
, "%d\n",
_SHOW(TTL
, ttl
, ttl
, "%u %u %u %u\n%u %u %u %u\n%u %u %u %u\n",
r
->thermal
.active
, r
->thermal
.since
, r
->thermal
.count
, r
->thermal
.time
,
r
->power
.active
, r
->power
.since
, r
->power
.count
, r
->power
.time
,
r
->alert
.active
, r
->alert
.since
, r
->alert
.count
, r
->alert
.time
);
_SHOW(CUTL
, cutl
, cutl
, "%u %u %u %llu\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n...\n",
r
->tck
, r
->core
, r
->thr
, r
->jif
,
arr64(&r
->sum
.user
, 4, BP
, 80),
arr64(&r
->cpu
[0].user
, 4, BP
+ 80, 80),
arr64(&r
->cpu
[1].user
, 4, BP
+ 160, 80),
arr64(&r
->cpu
[2].user
, 4, BP
+ 240, 80),
arr64(&r
->cpu
[3].user
, 4, BP
+ 320, 80),
arr64(&r
->cpu
[4].user
, 4, BP
+ 400, 80),
arr64(&r
->cpu
[5].user
, 4, BP
+ 480, 80),
arr64(&r
->cpu
[6].user
, 4, BP
+ 560, 80),
arr64(&r
->cpu
[7].user
, 4, BP
+ 640, 80))
_SHOW(MEM
, mem
, mem
, "%u %u %u\n",
r
->total
, r
->free
, r
->bufs
)
_SHOW(OS
, os
, os
, "%llu %llu %llu %llu %u [%s]\n",
r
->uptime
, r
->loads
[0], r
->loads
[1], r
->loads
[2],
r
->alen
, arr32(r
->apid
, r
->alen
, BP
, BL
))
* Ensure caller's creditials is root on all 'set' files.
* Even though file creation mode should prevent writes?
* - How many of the 'store's are to be permitted?
#define _STORE(op, nam) \
micras_store_##nam (struct class *class, \
struct class_attribute *attr, \
if (current_euid() != 0) \
if (count && buf[count - 1] == '\n') \
((char *) buf)[--count] = '\0'; \
val = simple_strtoul(buf, &ep, 0); \
if (ep != buf && !*ep) { \
wq = kmalloc(sizeof(* wq), GFP_KERNEL); \
wq->fnc = (int (*)(void *)) mr_set_##nam; \
wq->ptr = (void *) &val; \
if (! micras_mt_tsk(wq)) \
#if defined(CONFIG_MK1OM) || (defined(CONFIG_ML1OM) && USE_FSC)
* - Remove entries clst, cutl, mem, and os.
* Only included here for comparison with what cp/micinfo displays.
* They really need to go.
static struct class_attribute micras_attr
[] = {
__ATTR(hwinf
, 0444, micras_show_hwinf
, 0),
__ATTR(vers
, 0444, micras_show_vers
, 0),
__ATTR(freq
, 0644, micras_show_freq
, micras_store_freq
),
__ATTR(volt
, 0644, micras_show_volt
, micras_store_volt
),
#if defined(CONFIG_MK1OM) || (defined(CONFIG_ML1OM) && USE_FSC)
__ATTR(power
, 0444, micras_show_power
, 0),
__ATTR(plim
, 0644, micras_show_plim
, micras_store_plim
),
__ATTR(clst
, 0444, micras_show_clst
, 0),
__ATTR(gddr
, 0444, micras_show_gddr
, 0),
__ATTR(gfreq
, 0444, micras_show_gfreq
, 0),
__ATTR(gvolt
, 0444, micras_show_gvolt
, 0),
__ATTR(fan
, 0644, micras_show_fan
, micras_store_fan
),
__ATTR(temp
, 0444, micras_show_temp
, 0),
__ATTR(ecc
, 0444, micras_show_ecc
, 0),
__ATTR(trc
, 0644, micras_show_trc
, micras_store_trc
),
__ATTR(trbo
, 0644, micras_show_trbo
, micras_store_trbo
),
__ATTR(led
, 0644, micras_show_led
, micras_store_led
),
__ATTR(prochot
, 0444, micras_show_prochot
, 0),
__ATTR(pwralt
, 0444, micras_show_pwralt
, 0),
__ATTR(perst
, 0644, micras_show_perst
, micras_store_perst
),
__ATTR(ttl
, 0444, micras_show_ttl
, 0),
__ATTR(cutl
, 0444, micras_show_cutl
, 0),
__ATTR(mem
, 0444, micras_show_mem
, 0),
__ATTR(os
, 0444, micras_show_os
, 0),
static struct class ras_class
= {
.class_attrs
= micras_attr
,
** SCIF interface & services are mostly handled here, including
** all aspects of setting up and tearing down SCIF channels.
** We create three listening SCIF sockets and create a workqueue
** with the initial task of waiting for 'accept's to happen.
** When TTL or MC accept incoming connections, their workqueue
** task spawns one thread just to detect if/when peer closes
** the session and will block any further connects until thes
** service thread terminates (peer closes session).
** The TTL or MC event handler, executing in interrupt context,
** will check for an open session and if one is present, deliver
** their event record(s) on it by using scif_send().
** When CP accept incoming connections, its workqueue task spawns
** a new thread to run a session with the peer and then proceeds
** to accepting a new connection. Thus, there are no strict
** bounds on number of incoming connections, but for internal
** house-keeping sessions are limited to MR_SCIF_MAX (32).
** Accepted requests from the peer are fulfilled through the
** MT thread in a similar fashion as the sysctl interface, i.e.
** though function micras_mt_tsk(), who guarantee synchronized
** (serialized) access to MT core data and handle waits as needed.
** Function pointers corresponding to request opcodes are found
** by lookup in the fnc_map table.
** Note: This is not coded for maximum performance, since the
** use of the MT thread to serialize access to card data
** has a cost of two task switches attached, both which
** may cause delays due to other system activity.
static scif_epd_t micras_cp_lstn
; /* CP listener handle */
static struct workqueue_struct
* micras_cp_wq
; /* CP listener thread */
static atomic_t micras_cp_rst
; /* CP listener restart */
static struct delayed_work micras_cp_tkn
; /* CP accept token */
static DECLARE_BITMAP(micras_cp_fd
, MR_SCIF_MAX
); /* CP free slots */
static volatile struct scif_portID micras_cp_si
[MR_SCIF_MAX
]; /* CP sessions */
static volatile struct task_struct
* micras_cp_kt
[MR_SCIF_MAX
]; /* CP threads */
static volatile scif_epd_t micras_cp_ep
[MR_SCIF_MAX
]; /* CP handles */
static scif_epd_t micras_mc_lstn
; /* MC listener handle */
static struct workqueue_struct
* micras_mc_wq
; /* MC listener thread */
static struct delayed_work micras_mc_tkn
; /* MC accept token */
static volatile struct task_struct
* micras_mc_kt
; /* MC session */
static volatile scif_epd_t micras_mc_ep
; /* MC handle */
static scif_epd_t micras_ttl_lstn
; /* TTL listener handle */
static struct workqueue_struct
* micras_ttl_wq
; /* TTL listener thread */
static struct delayed_work micras_ttl_tkn
; /* TTL accept token */
static volatile struct task_struct
* micras_ttl_kt
; /* TTL session */
static volatile scif_epd_t micras_ttl_ep
; /* TTL handle */
micras_cp_sess(void * _slot
)
slot
= (uint32_t)((uint64_t) _slot
);
priv
= (micras_cp_si
[slot
].port
< 1024) ? 1 : 0;
printk("Scif: CP session %d running%s\n", slot
, priv
? " privileged" : "");
* Allocate local buffer from kernel
* Since the I/O buffers in SCIF is just one page,
* we'd never expect to need larger buffers here.
buf
= kmalloc(PAGE_SIZE
, GFP_KERNEL
);
printk("Scif: CP scratch pad alloc failed\n");
* Allocate a work queue task for the MT thread
wq
= kmalloc(sizeof(* wq
), GFP_KERNEL
);
printk("Scif: CP work task alloc failed\n");
* Start servicing MT protocol
len
= scif_recv(ep
, &q
, sizeof(q
), SCIF_RECV_BLOCK
);
printk("Scif: CP recv error %d\n", len
);
printk("Scif: CP short recv (%d), discarding\n", len
);
* - expected length (zero)
* - have callout in jump table
* - check requestor's port ID on privileged opcodes.
*TBD: opcodes above MR_REQ_MAX is really only meant for
* use by the PM module. Should it be host accessible?
if (q
.cmd
< MR_REQ_HWINF
||
#if defined(CONFIG_MK1OM) && USE_PM
printk("Scif: CP opcode %d invalid\n", q
.cmd
);
printk("Scif: CP command length %d invalid\n", q
.len
);
if (! fnc_map
[q
.cmd
].fnc
) {
printk("Scif: CP opcode %d un-implemented\n", q
.cmd
);
if (fnc_map
[q
.cmd
].privileged
&& !priv
) {
printk("Scif: CP opcode %d privileged, remote %d:%d\n",
q
.cmd
, micras_cp_si
[slot
].node
, micras_cp_si
[slot
].port
);
*TBD: If there is an error at this point, it might
* be a good idea to drain the SCIF channel.
* If garbage has entered the channel somehow,
* then how else can we get in sync such that
* next recv really is a command header?
* More radical solution is closing this session.
* If header is OK (blen still zero) then pass
* a work queue item to MT and wait for response.
* The result will end up in buf (payload for response)
* or an error code that can be sent back to requestor.
* Since we don't want to care about whether it is a
* get or set command here, the 'parm' value is copied
* into buf prior to passing the work item to MT.
* Thus, functions expecting an 'uint32_t *' to
* point to a new value will be satisfied.
if (fnc_map
[q
.cmd
].simple
) {
*((uint32_t *) buf
) = q
.parm
;
blen
= fnc_map
[q
.cmd
].fnc(buf
);
memset(wq
, '\0', sizeof(*wq
));
wq
->fnc
= (int (*)(void *)) fnc_map
[q
.cmd
].fnc
;
*((uint32_t *) buf
) = q
.parm
;
blen
= micras_mt_tsk(wq
);
* MT thread reported a failure.
* Set error bit and make error record in buf
((struct mr_err
*) buf
)->err
= -blen
;
((struct mr_err
*) buf
)->len
= 0;
a
.len
= sizeof(struct mr_err
);
* Payload size is set by call-out
* Send response header (always)
len
= scif_send(ep
, &a
, sizeof(a
), SCIF_SEND_BLOCK
);
printk("Scif: header send error %d\n", len
);
printk("Scif: CP short header send (%d of %lu)\n", len
, sizeof(a
));
* Send payload (if any, defined by a.len)
len
= scif_send(ep
, buf
, a
.len
, SCIF_SEND_BLOCK
);
printk("Scif: CP payload send error %d\n", len
);
printk("Scif: CP short payload send (%d of %d)\n", len
, a
.len
);
ep
= (scif_epd_t
) atomic64_xchg((atomic64_t
*)(micras_cp_ep
+ slot
), 0);
set_bit(slot
, micras_cp_fd
);
printk("Scif: CP session %d terminated, sess mask %lx\n", slot
, micras_cp_fd
[0]);
if (atomic_xchg(&micras_cp_rst
, 0)) {
printk("Scif: resume listener\n");
queue_delayed_work(micras_cp_wq
, &micras_cp_tkn
, 0);
* SCIF CP session launcher
micras_cp(struct work_struct
* work
)
struct task_struct
* thr
;
struct scif_portID sess_id
;
* Wait for somebody to connect to us
* We stop listening on any error whatsoever
err
= scif_accept(micras_cp_lstn
, &sess_id
, &sess_ep
, SCIF_ACCEPT_SYNC
);
printk("Scif: CP accept interrupted, error %d\n", err
);
printk("Scif: CP accept failed, error %d\n", err
);
printk("Scif: CP accept: remote %d:%d, local %d:%d\n",
sess_id
.node
, sess_id
.port
,
micras_cp_lstn
->port
.node
, micras_cp_lstn
->port
.port
);
* Spawn a new thread to run session with connecting peer
* We support only a limited number of connections, so first
* get a free "slot" for this session.
* The use of non-atomic ffs() below is safe as long as this
* function is never run by more than one thread at a time
* and all other manipulations of micras_cp_fd are atomic.
slot
= find_first_bit(micras_cp_fd
, MR_SCIF_MAX
);
if (slot
< MR_SCIF_MAX
) {
if (micras_cp_kt
[slot
] || micras_cp_ep
[slot
]) {
printk("Scif: CP slot %d busy (bug)\n", slot
);
clear_bit(slot
, micras_cp_fd
);
micras_cp_ep
[slot
] = sess_ep
;
micras_cp_si
[slot
] = sess_id
;
thr
= kthread_create(micras_cp_sess
, (void *)(uint64_t) slot
, "RAS CP svc %d", slot
);
printk("Scif: CP service thread creation failed\n");
set_bit(slot
, micras_cp_fd
);
micras_cp_kt
[slot
] = thr
;
printk("Scif: CP session %d launched, pid %d\n", slot
, thr
->pid
);
printk("Scif: No open session slots, closing session\n");
* Keep listening until session limit reached.
if (bitmap_weight(micras_cp_fd
, MR_SCIF_MAX
))
queue_delayed_work(micras_cp_wq
, &micras_cp_tkn
, 0);
printk("Scif: CP connection limit reached\n");
atomic_xchg(&micras_cp_rst
, 1);
micras_mc_sess(void * dummy
)
printk("Scif: MC session running\n");
* This is just to get indication if peer closes connection
* Sync with kernel MC event log.
* Try read 1 byte from host (turns into a wait-point
* keeping the connection open till host closes it)
len
= scif_recv(micras_mc_ep
, buf
, 1, SCIF_RECV_BLOCK
);
printk("Scif: MC recv error %d\n", len
);
* Ignore any received content.
ep
= (scif_epd_t
) atomic64_xchg((atomic64_t
*) &micras_mc_ep
, 0);
printk("Scif: MC session terminated\n");
* SCIF MC session launcher
micras_mc(struct work_struct
* work
)
struct task_struct
* thr
;
struct scif_portID sess_id
;
* Wait for somebody to connect to us
* We stop listening on any error whatsoever
err
= scif_accept(micras_mc_lstn
, &sess_id
, &sess_ep
, SCIF_ACCEPT_SYNC
);
printk("Scif: MC accept interrupted, error %d\n", err
);
printk("Scif: MC accept failed, error %d\n", err
);
printk("Scif: MC accept: remote %d:%d, local %d:%d\n",
sess_ep
->peer
.node
, sess_ep
->peer
.port
,
sess_ep
->port
.node
, sess_ep
->port
.port
);
* Spawn a new thread to run session with connecting peer
* We support only one connection, so if one already is
* running this one will be rejected.
thr
= kthread_create(micras_mc_sess
, 0, "RAS MC svc");
printk("Scif: MC service thread creation failed\n");
printk("Scif: MC connection limit reached\n");
queue_delayed_work(micras_mc_wq
, &micras_mc_tkn
, 0);
* Ship a pre-packaged machine check event record to host
micras_mc_send(struct mce_info
* mce
, int exc
)
err
= mr_sbox_rl(0, SBOX_THERMAL_STATUS_2
);
mce
->flags
|= PUT_BITS(15, 8, GET_BITS(19, 10, err
));
* Exception context SCIF access, can't sleep and can't
* wait on spinlocks either. May be detrimental to
* other scif communications, but this _is_ an emergency
* and we _do_ need to ship this message to the host.
err
= scif_send(micras_mc_ep
, mce
, sizeof(*mce
), SCIF_BLAST
);
ee_printk("micras_mc_send: scif_send failed, err %d\n", err
);
* Thread context SCIF access.
err
= scif_send(micras_mc_ep
, mce
, sizeof(*mce
), SCIF_SEND_BLOCK
);
printk("micras_mc_send: scif_send failed, err %d\n", err
);
return err
== sizeof(*mce
);
* SCIF TTL session thread
micras_ttl_sess(void * dummy
)
printk("Scif: TTL session running\n");
* This is just to get indication if peer closes connection
* Try read 1 byte from host (turns into a wait-point
* keeping the connection open till host closes it)
len
= scif_recv(micras_ttl_ep
, buf
, 1, SCIF_RECV_BLOCK
);
printk("Scif: TTL recv error %d\n", len
);
* Ignore any received content.
ep
= (scif_epd_t
) atomic64_xchg((atomic64_t
*) &micras_ttl_ep
, 0);
printk("Scif: TTL session terminated\n");
* SCIF TTL session launcher
micras_ttl(struct work_struct
* work
)
struct task_struct
* thr
;
struct scif_portID sess_id
;
* Wait for somebody to connect to us
* We stop listening on any error whatsoever
err
= scif_accept(micras_ttl_lstn
, &sess_id
, &sess_ep
, SCIF_ACCEPT_SYNC
);
printk("Scif: TTL accept interrupted, error %d\n", err
);
printk("Scif: TTL accept failed, error %d\n", err
);
printk("Scif: TTL accept: remote %d:%d, local %d:%d\n",
sess_ep
->peer
.node
, sess_ep
->peer
.port
,
sess_ep
->port
.node
, sess_ep
->port
.port
);
* Spawn a new thread to run session with connecting peer
* We support only one connection, so if one already is
* running this one will be rejected.
thr
= kthread_create(micras_ttl_sess
, 0, "RAS TTL svc");
printk("Scif: TTL service thread creation failed\n");
printk("Scif: TTL connection limit reached\n");
queue_delayed_work(micras_ttl_wq
, &micras_ttl_tkn
, 0);
* Ship a pre-packaged throttle event record to host
micras_ttl_send(struct ttl_info
* ttl
)
static struct ttl_info split_rec
;
cp
= ((char *) &split_rec
) + (sizeof(*ttl
) - split_rem
);
err
= scif_send(micras_ttl_ep
, cp
, split_rem
, 0);
* Tx of pendig buffer complete
* SCIF failed squarely, just drop the message.
err
= scif_send(micras_ttl_ep
, ttl
, sizeof(*ttl
), 0);
if (err
!= sizeof(*ttl
)) {
* Did not send all the message
* SCIF failed squarely, drop the message.
split_rem
= sizeof(*ttl
) - err
;
** MMIO regions used by RAS module
** Until some common strategy on access to BOXes and other CSRs
** we'll map them ourselves. All MMIO accesses are performed
** through 32 bit unsigned integers, but a 64 bit abstraction
** is provided for convenience (low 32 bit done first).
** We need access to the SBOX, all GBOXs, TBOXs and DBOXs.
** Note: I2C driver code for exception context in micras_elog.c
** has its own set of I/O routines in order to allow
uint8_t * micras_sbox
; /* SBOX mmio region */
uint8_t * micras_dbox
[DBOX_NUM
]; /* DBOX mmio region */
uint8_t * micras_gbox
[GBOX_NUM
]; /* GBOX mmio regions */
uint8_t * micras_tbox
[TBOX_NUM
]; /* TBOX mmio regions */
* Specials: some defines are currently missing
#define DBOX1_BASE 0x0800620000ULL
#define GBOX4_BASE 0x08006D0000ULL
#define GBOX5_BASE 0x08006C0000ULL
#define GBOX6_BASE 0x08006B0000ULL
#define GBOX7_BASE 0x08006A0000ULL
* MMIO I/O dumpers (for debug)
* Exception mode code needs to use the ee_print dumpers
* because printk is not safe to use (works most of the time
* though, but may hang the system eventually).
extern atomic_t pxa_block
;
#define RL if (! atomic_read(&pxa_block)) ee_print("%s: %4x -> %08x\n", __FUNCTION__, roff, val)
#define RQ if (! atomic_read(&pxa_block)) ee_print("%s: %4x -> %016llx\n", __FUNCTION__, roff, val)
#define WL if (! atomic_read(&pxa_block)) ee_print("%s: %4x <- %08x\n", __FUNCTION__, roff, val)
#define WQ if (! atomic_read(&pxa_block)) ee_print("%s: %4x <- %016llx\n", __FUNCTION__, roff, val)
#define RL printk("%s: %4x -> %08x\n", __FUNCTION__, roff, val)
#define RQ printk("%s: %4x -> %016llx\n", __FUNCTION__, roff, val)
#define WL printk("%s: %4x <- %08x\n", __FUNCTION__, roff, val)
#define WQ printk("%s: %4x <- %016llx\n", __FUNCTION__, roff, val)
#define RL /* As nothing */
#define RQ /* As nothing */
#define WL /* As nothing */
#define WQ /* As nothing */
* mr_sbox_base Return SBOX MMIO region
* mr_sbox_rl Read 32-bit register
* mr_sbox_rq Read 64-bit register (really two 32-bit reads)
* mr_sbox_wl Write 32-bit register
* mr_sbox_wq Write 64-bit register (really two 32-bit writes)
mr_sbox_rl(int dummy
, uint32_t roff
)
val
= * (volatile uint32_t *)(micras_sbox
+ roff
);
mr_sbox_rq(int dummy
, uint32_t roff
)
lo
= * (volatile uint32_t *)(micras_sbox
+ roff
);
hi
= * (volatile uint32_t *)(micras_sbox
+ roff
+ 4);
val
= ((uint64_t) hi
<< 32) | (uint64_t) lo
;
mr_sbox_wl(int dummy
, uint32_t roff
, uint32_t val
)
* (volatile uint32_t *)(micras_sbox
+ roff
) = val
;
mr_sbox_wq(int dummy
, uint32_t roff
, uint64_t val
)
* (volatile uint32_t *)(micras_sbox
+ roff
) = lo
;
* (volatile uint32_t *)(micras_sbox
+ roff
+ 4) = hi
;
* mr_dbox_base Return DBOX MMIO region
* mr_dbox_rl Read 32-bit register
* mr_dbox_rq Read 64-bit register (really two 32-bit reads)
* mr_dbox_wl Write 32-bit register
* mr_dbox_wq Write 64-bit register (really two 32-bit writes)
return micras_dbox
[unit
];
mr_dbox_rl(int unit
, uint32_t roff
)
val
= * (volatile uint32_t *)(micras_dbox
[unit
] + roff
);
mr_dbox_rq(int unit
, uint32_t roff
)
lo
= * (volatile uint32_t *)(micras_dbox
[unit
] + roff
);
hi
= * (volatile uint32_t *)(micras_dbox
[unit
] + roff
+ 4);
val
= ((uint64_t) hi
<< 32) | (uint64_t) lo
;
mr_dbox_wl(int unit
, uint32_t roff
, uint32_t val
)
* (volatile uint32_t *)(micras_dbox
[unit
] + roff
) = val
;
mr_dbox_wq(int unit
, uint32_t roff
, uint64_t val
)
* (volatile uint32_t *)(micras_dbox
[unit
] + roff
) = lo
;
* (volatile uint32_t *)(micras_dbox
[unit
] + roff
+ 4) = hi
;
* mr_gbox_base Return GBOX MMIO region
* mr_gbox_rl Read 32-bit register
* mr_gbox_rq Read 64-bit register (really two 32-bit reads)
* mr_gbox_wl Write 32-bit register
* mr_gbox_wq Write 64-bit register (really two 32-bit writes)
* Due to a Si bug, MMIO writes can be dropped by the GBOXs
* during heavy DMA activity (HSD #4844222). The risk of it
* happening is low enough that a 'repeat until it sticks'
* workaround is sufficient. No 'read' issues so far.
*TBD: Ramesh asked that GBOX MMIOs check for sleep states.
* Not sure how to do that, but here is a good spot to
* add such check, as all GBOX access comes thru here.
return micras_gbox
[unit
];
mr_gbox_rl(int unit
, uint32_t roff
)
val
= * (volatile uint32_t *)(micras_gbox
[unit
] + roff
);
mr_gbox_rq(int unit
, uint32_t roff
)
lo
= * (volatile uint32_t *)(micras_gbox
[unit
] + roff
);
* Instead of placing HI part of MCA_STATUS
* at 0x60 to form a natural 64-bit register,
* it located at 0xac, against all conventions.
hi
= * (volatile uint32_t *)(micras_gbox
[unit
] + 0xac);
hi
= * (volatile uint32_t *)(micras_gbox
[unit
] + roff
+ 4);
val
= ((uint64_t) hi
<< 32) | (uint64_t) lo
;
mr_gbox_wl(int unit
, uint32_t roff
, uint32_t val
)
* Due to bug HSD 4844222 loop until value sticks
for(rpt
= 10; rpt
-- ; ) {
* (volatile uint32_t *)(micras_gbox
[unit
] + roff
) = val
;
rb
= mr_gbox_rl(unit
, roff
);
mr_gbox_wq(int unit
, uint32_t roff
, uint64_t val
)
* Due to bug HSD 4844222 loop until value sticks
* Note: this may result in bad things happening if
* wrinting to a MMIO MCA STATUS register
* since there is a non-zero chance that the
* NMI handler can fire and change the register
* inside this loop. Require that the caller
* is on same CPU as the NMI handler (#0).
for(rpt
= 10; rpt
-- ; ) {
* (volatile uint32_t *)(micras_gbox
[unit
] + roff
) = lo
;
* Instead of placing HI part of MCA_STATUS
* at 0x60 to form a natural 64-bit register,
* it located at 0xac, against all conventions.
* (volatile uint32_t *)(micras_gbox
[unit
] + 0xac) = hi
;
* (volatile uint32_t *)(micras_gbox
[unit
] + roff
+ 4) = hi
;
rb
= mr_gbox_rq(unit
, roff
);
* mr_tbox_base Return TBOX MMIO region
* mr_tbox_rl Read 32-bit register
* mr_tbox_rq Read 64-bit register (really two 32-bit reads)
* mr_tbox_wl Write 32-bit register
* mr_tbox_wq Write 64-bit register (really two 32-bit writes)
* Some SKUs don't have TBOXs, in which case the
* micras_tbox array will contain null pointers.
* We do not test for this here, but expect that
* caller either know what he's doing or consult
* the mr_tbox_base() function first.
return micras_tbox
[unit
];
mr_tbox_rl(int unit
, uint32_t roff
)
val
= * (volatile uint32_t *)(micras_tbox
[unit
] + roff
);
mr_tbox_rq(int unit
, uint32_t roff
)
lo
= * (volatile uint32_t *)(micras_tbox
[unit
] + roff
);
hi
= * (volatile uint32_t *)(micras_tbox
[unit
] + roff
+ 4);
val
= ((uint64_t) hi
<< 32) | (uint64_t) lo
;
mr_tbox_wl(int unit
, uint32_t roff
, uint32_t val
)
* (volatile uint32_t *)(micras_tbox
[unit
] + roff
) = val
;
mr_tbox_wq(int unit
, uint32_t roff
, uint64_t val
)
* (volatile uint32_t *)(micras_tbox
[unit
] + roff
) = lo
;
* (volatile uint32_t *)(micras_tbox
[unit
] + roff
+ 4) = hi
;
** SMP utilities for CP and MC.
** The kernel offers routines for MSRs, but as far
** as I could find then there isn't any for some
** CPU registers we need, like CR4.
** rd_cr4_on_cpu Read a CR4 value on CPU
** set_in_cr4_on_cpu Set bits in CR4 on a CPU
** clear_in_cr4_on_cpu Guess...
** rdtsc Read time stamp counter
**TBD: Special case when CPU happens to be current?
*((uint32_t *) p
) = read_cr4();
smp_call_function_single(cpu
, _rd_cr4_on_cpu
, &cr4
, 1);
_set_in_cr4_on_cpu(void * p
)
set_in_cr4_on_cpu(int cpu
, uint32_t m
)
smp_call_function_single(cpu
, _set_in_cr4_on_cpu
, &m
, 1);
_clear_in_cr4_on_cpu(void * p
)
cr4
&= ~ *(uint32_t *) p
;
clear_in_cr4_on_cpu(int cpu
, uint32_t m
)
smp_call_function_single(cpu
, _clear_in_cr4_on_cpu
, &m
, 1);
__asm__
__volatile__ ("rdtsc" : "=a" (lo
), "=d" (hi
));
return ((uint64_t) hi
) << 32 | lo
;
** Module load/unload logic
* Startup job (run by MT thread)
* Intended to handle tasks that cannot impact
* module load status, such as kicking off service
micras_init2(struct work_struct
* work
)
* Make MT one-time setup and kick
* off 1 sec timer and SCIF listeners
INIT_DELAYED_WORK(&micras_wq_tick
, micras_mt_tick
);
queue_delayed_work(micras_wq
, &micras_wq_tick
, msecs_to_jiffies(5000));
bitmap_fill(micras_cp_fd
, MR_SCIF_MAX
);
INIT_DELAYED_WORK(&micras_cp_tkn
, micras_cp
);
queue_delayed_work(micras_cp_wq
, &micras_cp_tkn
, 0);
INIT_DELAYED_WORK(&micras_mc_tkn
, micras_mc
);
queue_delayed_work(micras_mc_wq
, &micras_mc_tkn
, 0);
INIT_DELAYED_WORK(&micras_ttl_tkn
, micras_ttl
);
queue_delayed_work(micras_ttl_wq
, &micras_ttl_tkn
, 0);
#if defined(CONFIG_MK1OM) && WA_4845465 && DIE_PROC
die_pe
= proc_create("die", 0644, 0, &proc_die_operations
);
printk("RAS.init: module operational\n");
printk("Loading RAS module ver %s. Build date: %s\n", RAS_VER
, __DATE__
);
* Create work queue for the monitoring thread
* and pass it some initial work to start with.
#if defined(CONFIG_MK1OM) && WA_4845465
micras_wq
= alloc_workqueue("RAS MT", WQ_HIGHPRI
| WQ_UNBOUND
| WQ_MEM_RECLAIM
, 1);
micras_wq
= create_singlethread_workqueue("RAS MT");
printk("RAS.init: cannot start work queue, error %d\n", err
);
* Register top sysfs class (directory) and attach attributes (files)
* beneath it. No 'device's involved.
err
= class_register(&ras_class
);
printk("RAS.init: cannot register class 'micras', error %d\n", err
);
* Setup CP SCIF port in listening mode
micras_cp_lstn
= scif_open();
printk("RAS.init: cannot get SCIF CP endpoint\n");
err
= scif_bind(micras_cp_lstn
, MR_MON_PORT
);
printk("RAS.init: cannot bind SCIF CP endpoint, error %d\n", err
);
err
= scif_listen(micras_cp_lstn
, MR_SCIF_MAX
);
printk("RAS.init: cannot make SCIF CP listen, error %d\n", err
);
micras_cp_wq
= create_singlethread_workqueue("RAS CP listen");
printk("RAS.init: cannot start CP listener work queue, error %d\n", err
);
* Setup MC SCIF port in listening mode
micras_mc_lstn
= scif_open();
printk("RAS.init: cannot get SCIF MC endpoint\n");
err
= scif_bind(micras_mc_lstn
, MR_MCE_PORT
);
printk("RAS.init: cannot bind SCIF MC endpoint, error %d\n", err
);
err
= scif_listen(micras_mc_lstn
, MR_SCIF_MAX
);
printk("RAS.init: cannot make SCIF MC listen, error %d\n", err
);
micras_mc_wq
= create_singlethread_workqueue("RAS MC listen");
printk("RAS.init: cannot start listener work queue, error %d\n", err
);
* Setup TTL SCIF port in listening mode
micras_ttl_lstn
= scif_open();
printk("RAS.init: cannot get SCIF TTL endpoint\n");
err
= scif_bind(micras_ttl_lstn
, MR_TTL_PORT
);
printk("RAS.init: cannot bind SCIF TTL endpoint, error %d\n", err
);
err
= scif_listen(micras_ttl_lstn
, MR_SCIF_MAX
);
printk("RAS.init: cannot make SCIF TTL listen, error %d\n", err
);
micras_ttl_wq
= create_singlethread_workqueue("RAS TTL listen");
printk("RAS.init: cannot start listener work queue, error %d\n", err
);
* Make the MMIO maps we need.
micras_sbox
= ioremap(SBOX_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_dbox
[0] = ioremap(DBOX0_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_dbox
[1] = ioremap(DBOX1_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_gbox
[0] = ioremap(GBOX0_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_gbox
[1] = ioremap(GBOX1_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_gbox
[2] = ioremap(GBOX2_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_gbox
[3] = ioremap(GBOX3_BASE
, COMMON_MMIO_BOX_SIZE
);
if (!micras_gbox
[0] || !micras_gbox
[1] ||
!micras_gbox
[2] || !micras_gbox
[3])
micras_gbox
[4] = ioremap(GBOX4_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_gbox
[5] = ioremap(GBOX5_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_gbox
[6] = ioremap(GBOX6_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_gbox
[7] = ioremap(GBOX7_BASE
, COMMON_MMIO_BOX_SIZE
);
if (!micras_gbox
[4] || !micras_gbox
[5] ||
!micras_gbox
[6] || !micras_gbox
[7])
* Most SKUs don't have TBOXes.
* If not, then don't map to their MMIO space
micras_tbox
[0] = ioremap(TXS0_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_tbox
[1] = ioremap(TXS1_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_tbox
[2] = ioremap(TXS2_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_tbox
[3] = ioremap(TXS3_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_tbox
[4] = ioremap(TXS4_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_tbox
[5] = ioremap(TXS5_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_tbox
[6] = ioremap(TXS6_BASE
, COMMON_MMIO_BOX_SIZE
);
micras_tbox
[7] = ioremap(TXS7_BASE
, COMMON_MMIO_BOX_SIZE
);
if (!micras_tbox
[0] || !micras_tbox
[1] ||
!micras_tbox
[2] || !micras_tbox
[3] ||
!micras_tbox
[4] || !micras_tbox
[5] ||
!micras_tbox
[6] || !micras_tbox
[7])
* Setup non-volatile MC error logging device.
* Setup core MC event handler.
* If this can't fail, move into micras_wq_init instead.
* Setup un-core MC event handler.
* If this can't fail, move into micras_wq_init instead.
#if defined(CONFIG_MK1OM) && USE_PM
#if defined(CONFIG_MK1OM) && WA_4845465
* Launch SMC temperature push work.
* Supported by SMC firmware later than 121.11 (build 4511).
extern int mr_smc_rd(uint8_t, uint32_t *);
ref
= PUT_BITS(31, 24, 121) |
INIT_DELAYED_WORK(&micras_wq_smc
, micras_mt_smc
);
queue_delayed_work(micras_wq
, &micras_wq_smc
, 0);
printk("RAS.init: HSD 4845465 workaround active, fw %x\n", rev
);
printk("RAS.init: SMC too old for HSD 4845465 workaround, fw %x\n", rev
);
* Launch deferable setup work
try_module_get(THIS_MODULE
);
INIT_DELAYED_WORK(&micras_wq_init
, micras_init2
);
queue_delayed_work(micras_wq
, &micras_wq_init
, msecs_to_jiffies(500));
printk("RAS module load completed\n");
* Error exits: unwind all setup done so far and return failure
*TBD: consider calling exit function. Requires that it can tell
* with certainty what has been setup and what hasn't.
#if defined(CONFIG_MK1OM) && USE_PM
for(i
= 0; i
< ARRAY_SIZE(micras_dbox
); i
++)
for(i
= 0; i
< ARRAY_SIZE(micras_gbox
); i
++)
for(i
= 0; i
< ARRAY_SIZE(micras_tbox
); i
++)
destroy_workqueue(micras_ttl_wq
);
scif_close(micras_ttl_lstn
);
destroy_workqueue(micras_mc_wq
);
scif_close(micras_mc_lstn
);
destroy_workqueue(micras_cp_wq
);
scif_close(micras_cp_lstn
);
class_unregister(&ras_class
);
flush_workqueue(micras_wq
);
destroy_workqueue(micras_wq
);
printk("RAS module load failed\n");
printk("Unloading RAS module\n");
* Disconnect MC event handlers and
* close the I2C eeprom interfaces.
* Close SCIF listeners (no more connects).
scif_close(micras_cp_lstn
);
scif_close(micras_mc_lstn
);
scif_close(micras_ttl_lstn
);
destroy_workqueue(micras_cp_wq
);
destroy_workqueue(micras_mc_wq
);
destroy_workqueue(micras_ttl_wq
);
* Terminate active sessions by closing their end points.
* Session threads then should clean up after themselves.
for(i
= 0; i
< MR_SCIF_MAX
; i
++) {
printk("RAS.exit: force closing CP session %d\n", i
);
ep
= (scif_epd_t
) atomic64_xchg((atomic64_t
*)(micras_cp_ep
+ i
), 0);
for(i
= 0; i
< 1000; i
++) {
if (bitmap_weight(micras_cp_fd
, MR_SCIF_MAX
) == MR_SCIF_MAX
)
printk("RAS.exit: force closing MC session\n");
ep
= (scif_epd_t
) atomic64_xchg((atomic64_t
*) &micras_mc_ep
, 0);
for(i
= 0; (i
< 1000) && micras_mc_kt
; i
++)
printk("RAS.exit: force closing TTL session\n");
ep
= (scif_epd_t
) atomic64_xchg((atomic64_t
*) &micras_ttl_ep
, 0);
for(i
= 0; (i
< 1000) && micras_ttl_kt
; i
++)
* Tear down sysfs class and its nodes
class_unregister(&ras_class
);
#if defined(CONFIG_MK1OM) && USE_PM
* De-register with the PM module.
* Shut down the work queues
#if defined(CONFIG_MK1OM) && WA_4845465
cancel_delayed_work(&micras_wq_smc
);
cancel_delayed_work(&micras_wq_tick
);
cancel_delayed_work(&micras_wq_init
);
flush_workqueue(micras_wq
);
destroy_workqueue(micras_wq
);
* Remove MMIO region maps
for(i
= 0; i
< ARRAY_SIZE(micras_dbox
); i
++)
for(i
= 0; i
< ARRAY_SIZE(micras_gbox
); i
++)
for(i
= 0; i
< ARRAY_SIZE(micras_tbox
); i
++)
#if defined(CONFIG_MK1OM) && WA_4845465 && DIE_PROC
if (smc_4845465
&& die_pe
) {
remove_proc_entry("die", 0);
printk("RAS module unload completed\n");
module_init(micras_init
);
module_exit(micras_exit
);
MODULE_AUTHOR("Intel Corp. 2013 (" __DATE__
") ver " RAS_VER
);
MODULE_DESCRIPTION("RAS and HW monitoring module for MIC");