⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 cmf.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 3 页
字号:
/* * 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(&copy_block->wait);	kref_init(&copy_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(&copy_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(&copy_block->kref);	copy_block->ret = cmf_copy_block(cdev);	wake_up(&copy_block->wait);	kref_put(&copy_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 + -