📄 cmf.c
字号:
/* * linux/drivers/s390/cio/cmf.c * * Linux on zSeries Channel Measurement Facility support * * Copyright 2000,2006 IBM Corporation * * Authors: Arnd Bergmann <arndb@de.ibm.com> * Cornelia Huck <cornelia.huck@de.ibm.com> * * original idea from Natarajan Krishnaswami <nkrishna@us.ibm.com> * * This program 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; either version 2, or (at your option) * any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */#include <linux/bootmem.h>#include <linux/device.h>#include <linux/init.h>#include <linux/list.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/slab.h>#include <linux/timex.h> /* get_clock() */#include <asm/ccwdev.h>#include <asm/cio.h>#include <asm/cmb.h>#include <asm/div64.h>#include "cio.h"#include "css.h"#include "device.h"#include "ioasm.h"#include "chsc.h"/* * parameter to enable cmf during boot, possible uses are: * "s390cmf" -- enable cmf and allocate 2 MB of ram so measuring can be * used on any subchannel * "s390cmf=<num>" -- enable cmf and allocate enough memory to measure * <num> subchannel, where <num> is an integer * between 1 and 65535, default is 1024 */#define ARGSTRING "s390cmf"/* indices for READCMB */enum cmb_index { /* basic and exended format: */ cmb_ssch_rsch_count, cmb_sample_count, cmb_device_connect_time, cmb_function_pending_time, cmb_device_disconnect_time, cmb_control_unit_queuing_time, cmb_device_active_only_time, /* extended format only: */ cmb_device_busy_time, cmb_initial_command_response_time,};/** * enum cmb_format - types of supported measurement block formats * * @CMF_BASIC: traditional channel measurement blocks supported * by all machines that we run on * @CMF_EXTENDED: improved format that was introduced with the z990 * machine * @CMF_AUTODETECT: default: use extended format when running on a machine * supporting extended format, otherwise fall back to * basic format */enum cmb_format { CMF_BASIC, CMF_EXTENDED, CMF_AUTODETECT = -1,};/* * format - actual format for all measurement blocks * * The format module parameter can be set to a value of 0 (zero) * or 1, indicating basic or extended format as described for * enum cmb_format. */static int format = CMF_AUTODETECT;module_param(format, bool, 0444);/** * struct cmb_operations - functions to use depending on cmb_format * * Most of these functions operate on a struct ccw_device. There is only * one instance of struct cmb_operations because the format of the measurement * data is guaranteed to be the same for every ccw_device. * * @alloc: allocate memory for a channel measurement block, * either with the help of a special pool or with kmalloc * @free: free memory allocated with @alloc * @set: enable or disable measurement * @read: read a measurement entry at an index * @readall: read a measurement block in a common format * @reset: clear the data in the associated measurement block and * reset its time stamp * @align: align an allocated block so that the hardware can use it */struct cmb_operations { int (*alloc) (struct ccw_device *); void (*free) (struct ccw_device *); int (*set) (struct ccw_device *, u32); u64 (*read) (struct ccw_device *, int); int (*readall)(struct ccw_device *, struct cmbdata *); void (*reset) (struct ccw_device *); void *(*align) (void *);/* private: */ struct attribute_group *attr_group;};static struct cmb_operations *cmbops;struct cmb_data { void *hw_block; /* Pointer to block updated by hardware */ void *last_block; /* Last changed block copied from hardware block */ int size; /* Size of hw_block and last_block */ unsigned long long last_update; /* when last_block was updated */};/* * Our user interface is designed in terms of nanoseconds, * while the hardware measures total times in its own * unit. */static inline u64 time_to_nsec(u32 value){ return ((u64)value) * 128000ull;}/* * Users are usually interested in average times, * not accumulated time. * This also helps us with atomicity problems * when reading sinlge values. */static inline u64 time_to_avg_nsec(u32 value, u32 count){ u64 ret; /* no samples yet, avoid division by 0 */ if (count == 0) return 0; /* value comes in units of 128 µsec */ ret = time_to_nsec(value); do_div(ret, count); return ret;}/* * Activate or deactivate the channel monitor. When area is NULL, * the monitor is deactivated. The channel monitor needs to * be active in order to measure subchannels, which also need * to be enabled. */static inline void cmf_activate(void *area, unsigned int onoff){ register void * __gpr2 asm("2"); register long __gpr1 asm("1"); __gpr2 = area; __gpr1 = onoff ? 2 : 0; /* activate channel measurement */ asm("schm" : : "d" (__gpr2), "d" (__gpr1) );}static int set_schib(struct ccw_device *cdev, u32 mme, int mbfc, unsigned long address){ int ret; int retry; struct subchannel *sch; struct schib *schib; sch = to_subchannel(cdev->dev.parent); schib = &sch->schib; /* msch can silently fail, so do it again if necessary */ for (retry = 0; retry < 3; retry++) { /* prepare schib */ stsch(sch->schid, schib); schib->pmcw.mme = mme; schib->pmcw.mbfc = mbfc; /* address can be either a block address or a block index */ if (mbfc) schib->mba = address; else schib->pmcw.mbi = address; /* try to submit it */ switch(ret = msch_err(sch->schid, schib)) { case 0: break; case 1: case 2: /* in I/O or status pending */ ret = -EBUSY; break; case 3: /* subchannel is no longer valid */ ret = -ENODEV; break; default: /* msch caught an exception */ ret = -EINVAL; break; } stsch(sch->schid, schib); /* restore the schib */ if (ret) break; /* check if it worked */ if (schib->pmcw.mme == mme && schib->pmcw.mbfc == mbfc && (mbfc ? (schib->mba == address) : (schib->pmcw.mbi == address))) return 0; ret = -EINVAL; } return ret;}struct set_schib_struct { u32 mme; int mbfc; unsigned long address; wait_queue_head_t wait; int ret; struct kref kref;};static void cmf_set_schib_release(struct kref *kref){ struct set_schib_struct *set_data; set_data = container_of(kref, struct set_schib_struct, kref); kfree(set_data);}#define CMF_PENDING 1static int set_schib_wait(struct ccw_device *cdev, u32 mme, int mbfc, unsigned long address){ struct set_schib_struct *set_data; int ret; spin_lock_irq(cdev->ccwlock); if (!cdev->private->cmb) { ret = -ENODEV; goto out; } set_data = kzalloc(sizeof(struct set_schib_struct), GFP_ATOMIC); if (!set_data) { ret = -ENOMEM; goto out; } init_waitqueue_head(&set_data->wait); kref_init(&set_data->kref); set_data->mme = mme; set_data->mbfc = mbfc; set_data->address = address; ret = set_schib(cdev, mme, mbfc, address); if (ret != -EBUSY) goto out_put; if (cdev->private->state != DEV_STATE_ONLINE) { /* if the device is not online, don't even try again */ ret = -EBUSY; goto out_put; } cdev->private->state = DEV_STATE_CMFCHANGE; set_data->ret = CMF_PENDING; cdev->private->cmb_wait = set_data; spin_unlock_irq(cdev->ccwlock); if (wait_event_interruptible(set_data->wait, set_data->ret != CMF_PENDING)) { spin_lock_irq(cdev->ccwlock); if (set_data->ret == CMF_PENDING) { set_data->ret = -ERESTARTSYS; if (cdev->private->state == DEV_STATE_CMFCHANGE) cdev->private->state = DEV_STATE_ONLINE; } spin_unlock_irq(cdev->ccwlock); } spin_lock_irq(cdev->ccwlock); cdev->private->cmb_wait = NULL; ret = set_data->ret;out_put: kref_put(&set_data->kref, cmf_set_schib_release);out: spin_unlock_irq(cdev->ccwlock); return ret;}void retry_set_schib(struct ccw_device *cdev){ struct set_schib_struct *set_data; set_data = cdev->private->cmb_wait; if (!set_data) { WARN_ON(1); return; } kref_get(&set_data->kref); set_data->ret = set_schib(cdev, set_data->mme, set_data->mbfc, set_data->address); wake_up(&set_data->wait); kref_put(&set_data->kref, cmf_set_schib_release);}static int cmf_copy_block(struct ccw_device *cdev){ struct subchannel *sch; void *reference_buf; void *hw_block; struct cmb_data *cmb_data; sch = to_subchannel(cdev->dev.parent); if (stsch(sch->schid, &sch->schib)) return -ENODEV; if (sch->schib.scsw.fctl & SCSW_FCTL_START_FUNC) { /* Don't copy if a start function is in progress. */ if ((!(sch->schib.scsw.actl & SCSW_ACTL_SUSPENDED)) && (sch->schib.scsw.actl & (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT)) && (!(sch->schib.scsw.stctl & SCSW_STCTL_SEC_STATUS))) return -EBUSY; } cmb_data = cdev->private->cmb; hw_block = cmbops->align(cmb_data->hw_block); if (!memcmp(cmb_data->last_block, hw_block, cmb_data->size)) /* No need to copy. */ return 0; reference_buf = kzalloc(cmb_data->size, GFP_ATOMIC); if (!reference_buf) return -ENOMEM; /* Ensure consistency of block copied from hardware. */ do { memcpy(cmb_data->last_block, hw_block, cmb_data->size); memcpy(reference_buf, hw_block, cmb_data->size); } while (memcmp(cmb_data->last_block, reference_buf, cmb_data->size)); cmb_data->last_update = get_clock(); kfree(reference_buf); return 0;}struct copy_block_struct { wait_queue_head_t wait; int ret; struct kref kref;};static void cmf_copy_block_release(struct kref *kref){ struct copy_block_struct *copy_block; copy_block = container_of(kref, struct copy_block_struct, kref); kfree(copy_block);}static int cmf_cmb_copy_wait(struct ccw_device *cdev){ struct copy_block_struct *copy_block; int ret; unsigned long flags; spin_lock_irqsave(cdev->ccwlock, flags); if (!cdev->private->cmb) { ret = -ENODEV; goto out; } copy_block = kzalloc(sizeof(struct copy_block_struct), GFP_ATOMIC); if (!copy_block) { ret = -ENOMEM; goto out; } init_waitqueue_head(©_block->wait); kref_init(©_block->kref); ret = cmf_copy_block(cdev); if (ret != -EBUSY) goto out_put; if (cdev->private->state != DEV_STATE_ONLINE) { ret = -EBUSY; goto out_put; } cdev->private->state = DEV_STATE_CMFUPDATE; copy_block->ret = CMF_PENDING; cdev->private->cmb_wait = copy_block; spin_unlock_irqrestore(cdev->ccwlock, flags); if (wait_event_interruptible(copy_block->wait, copy_block->ret != CMF_PENDING)) { spin_lock_irqsave(cdev->ccwlock, flags); if (copy_block->ret == CMF_PENDING) { copy_block->ret = -ERESTARTSYS; if (cdev->private->state == DEV_STATE_CMFUPDATE) cdev->private->state = DEV_STATE_ONLINE; } spin_unlock_irqrestore(cdev->ccwlock, flags); } spin_lock_irqsave(cdev->ccwlock, flags); cdev->private->cmb_wait = NULL; ret = copy_block->ret;out_put: kref_put(©_block->kref, cmf_copy_block_release);out: spin_unlock_irqrestore(cdev->ccwlock, flags); return ret;}void cmf_retry_copy_block(struct ccw_device *cdev){ struct copy_block_struct *copy_block; copy_block = cdev->private->cmb_wait; if (!copy_block) { WARN_ON(1); return; } kref_get(©_block->kref); copy_block->ret = cmf_copy_block(cdev); wake_up(©_block->wait); kref_put(©_block->kref, cmf_copy_block_release);}static void cmf_generic_reset(struct ccw_device *cdev){ struct cmb_data *cmb_data; spin_lock_irq(cdev->ccwlock); cmb_data = cdev->private->cmb; if (cmb_data) { memset(cmb_data->last_block, 0, cmb_data->size); /* * Need to reset hw block as well to make the hardware start * from 0 again.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -