aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/scsi/ses.c
blob: 1bcf3c33d7ff017fbc3043197569d3042705f837 (plain) (tree)


































                                                                               


                              
























                                                                      
                             




                                                                 
                               
















                                                                         
                               

















                                                                            
                                                          

                                                        
                                                                     
                                                   

                                                                           





















                                                                            
                                                                             


                                                                        
                                                                     
                                                   

                                                                           


















                                                                            
                            

                                                     

                                                     





                                                              
                                     


















                                                             
                            

                                                     

                                                 




                                                             
                            

                                                     

                                                         





                                                               
                                     


















                                                               
                                     



































                                                                       
                                                    

                         
                                                 







































































                                                                     
                           





                                                                   
                    


















                                                                               










                                                                      
                       
                                      































                                                                           
                                            

                                                     
                                                                
                                     

                                                                         

                                   
                                                            

                                      
                                           


                                                           
                                                                 

                                                           
                                                






















                                                                               
                                    







                                                 




                                                  
 
                                      





                                                                    


                                 















                                                                  
                   



                                                                   













                                                           
 
                                                                               
                   
                              
 
                                                                         







                                                                        
                                                       

                                                    



                                                                  
                                                             

                                               

                                     











                                                       

                                                            








                                                            









                                                                               
                         



                                                                              


                                                                          

                                                                    

                                                                              
                         





                                                                      




















                                                                           
                     














                                                                          
                                                

                                                         
                                                                





                                         
                                            





                                
                               





                                          
                                



                                               

                                          











































                                                           
/*
 * SCSI Enclosure Services
 *
 * Copyright (C) 2008 James Bottomley <James.Bottomley@HansenPartnership.com>
 *
**-----------------------------------------------------------------------------
**
**  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.
**
**  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/module.h>
#include <linux/kernel.h>
#include <linux/enclosure.h>

#include <scsi/scsi.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_dbg.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_driver.h>
#include <scsi/scsi_host.h>

struct ses_device {
	unsigned char *page1;
	unsigned char *page2;
	unsigned char *page10;
	short page1_len;
	short page2_len;
	short page10_len;
};

struct ses_component {
	u64 addr;
	unsigned char *desc;
};

static int ses_probe(struct device *dev)
{
	struct scsi_device *sdev = to_scsi_device(dev);
	int err = -ENODEV;

	if (sdev->type != TYPE_ENCLOSURE)
		goto out;

	err = 0;
	sdev_printk(KERN_NOTICE, sdev, "Attached Enclosure device\n");

 out:
	return err;
}

#define SES_TIMEOUT (30 * HZ)
#define SES_RETRIES 3

static int ses_recv_diag(struct scsi_device *sdev, int page_code,
			 void *buf, int bufflen)
{
	unsigned char cmd[] = {
		RECEIVE_DIAGNOSTIC,
		1,		/* Set PCV bit */
		page_code,
		bufflen >> 8,
		bufflen & 0xff,
		0
	};

	return scsi_execute_req(sdev, cmd, DMA_FROM_DEVICE, buf, bufflen,
				NULL, SES_TIMEOUT, SES_RETRIES);
}

static int ses_send_diag(struct scsi_device *sdev, int page_code,
			 void *buf, int bufflen)
{
	u32 result;

	unsigned char cmd[] = {
		SEND_DIAGNOSTIC,
		0x10,		/* Set PF bit */
		0,
		bufflen >> 8,
		bufflen & 0xff,
		0
	};

	result = scsi_execute_req(sdev, cmd, DMA_TO_DEVICE, buf, bufflen,
				  NULL, SES_TIMEOUT, SES_RETRIES);
	if (result)
		sdev_printk(KERN_ERR, sdev, "SEND DIAGNOSTIC result: %8x\n",
			    result);
	return result;
}

static int ses_set_page2_descriptor(struct enclosure_device *edev,
				      struct enclosure_component *ecomp,
				      unsigned char *desc)
{
	int i, j, count = 0, descriptor = ecomp->number;
	struct scsi_device *sdev = to_scsi_device(edev->edev.parent);
	struct ses_device *ses_dev = edev->scratch;
	unsigned char *type_ptr = ses_dev->page1 + 12 + ses_dev->page1[11];
	unsigned char *desc_ptr = ses_dev->page2 + 8;

	/* Clear everything */
	memset(desc_ptr, 0, ses_dev->page2_len - 8);
	for (i = 0; i < ses_dev->page1[10]; i++, type_ptr += 4) {
		for (j = 0; j < type_ptr[1]; j++) {
			desc_ptr += 4;
			if (type_ptr[0] != ENCLOSURE_COMPONENT_DEVICE &&
			    type_ptr[0] != ENCLOSURE_COMPONENT_ARRAY_DEVICE)
				continue;
			if (count++ == descriptor) {
				memcpy(desc_ptr, desc, 4);
				/* set select */
				desc_ptr[0] |= 0x80;
				/* clear reserved, just in case */
				desc_ptr[0] &= 0xf0;
			}
		}
	}

	return ses_send_diag(sdev, 2, ses_dev->page2, ses_dev->page2_len);
}

static unsigned char *ses_get_page2_descriptor(struct enclosure_device *edev,
				      struct enclosure_component *ecomp)
{
	int i, j, count = 0, descriptor = ecomp->number;
	struct scsi_device *sdev = to_scsi_device(edev->edev.parent);
	struct ses_device *ses_dev = edev->scratch;
	unsigned char *type_ptr = ses_dev->page1 + 12 + ses_dev->page1[11];
	unsigned char *desc_ptr = ses_dev->page2 + 8;

	ses_recv_diag(sdev, 2, ses_dev->page2, ses_dev->page2_len);

	for (i = 0; i < ses_dev->page1[10]; i++, type_ptr += 4) {
		for (j = 0; j < type_ptr[1]; j++) {
			desc_ptr += 4;
			if (type_ptr[0] != ENCLOSURE_COMPONENT_DEVICE &&
			    type_ptr[0] != ENCLOSURE_COMPONENT_ARRAY_DEVICE)
				continue;
			if (count++ == descriptor)
				return desc_ptr;
		}
	}
	return NULL;
}

static void ses_get_fault(struct enclosure_device *edev,
			  struct enclosure_component *ecomp)
{
	unsigned char *desc;

	desc = ses_get_page2_descriptor(edev, ecomp);
	if (desc)
		ecomp->fault = (desc[3] & 0x60) >> 4;
}

static int ses_set_fault(struct enclosure_device *edev,
			  struct enclosure_component *ecomp,
			 enum enclosure_component_setting val)
{
	unsigned char desc[4] = {0 };

	switch (val) {
	case ENCLOSURE_SETTING_DISABLED:
		/* zero is disabled */
		break;
	case ENCLOSURE_SETTING_ENABLED:
		desc[2] = 0x02;
		break;
	default:
		/* SES doesn't do the SGPIO blink settings */
		return -EINVAL;
	}

	return ses_set_page2_descriptor(edev, ecomp, desc);
}

static void ses_get_status(struct enclosure_device *edev,
			   struct enclosure_component *ecomp)
{
	unsigned char *desc;

	desc = ses_get_page2_descriptor(edev, ecomp);
	if (desc)
		ecomp->status = (desc[0] & 0x0f);
}

static void ses_get_locate(struct enclosure_device *edev,
			   struct enclosure_component *ecomp)
{
	unsigned char *desc;

	desc = ses_get_page2_descriptor(edev, ecomp);
	if (desc)
		ecomp->locate = (desc[2] & 0x02) ? 1 : 0;
}

static int ses_set_locate(struct enclosure_device *edev,
			  struct enclosure_component *ecomp,
			  enum enclosure_component_setting val)
{
	unsigned char desc[4] = {0 };

	switch (val) {
	case ENCLOSURE_SETTING_DISABLED:
		/* zero is disabled */
		break;
	case ENCLOSURE_SETTING_ENABLED:
		desc[2] = 0x02;
		break;
	default:
		/* SES doesn't do the SGPIO blink settings */
		return -EINVAL;
	}
	return ses_set_page2_descriptor(edev, ecomp, desc);
}

static int ses_set_active(struct enclosure_device *edev,
			  struct enclosure_component *ecomp,
			  enum enclosure_component_setting val)
{
	unsigned char desc[4] = {0 };

	switch (val) {
	case ENCLOSURE_SETTING_DISABLED:
		/* zero is disabled */
		ecomp->active = 0;
		break;
	case ENCLOSURE_SETTING_ENABLED:
		desc[2] = 0x80;
		ecomp->active = 1;
		break;
	default:
		/* SES doesn't do the SGPIO blink settings */
		return -EINVAL;
	}
	return ses_set_page2_descriptor(edev, ecomp, desc);
}

static struct enclosure_component_callbacks ses_enclosure_callbacks = {
	.get_fault		= ses_get_fault,
	.set_fault		= ses_set_fault,
	.get_status		= ses_get_status,
	.get_locate		= ses_get_locate,
	.set_locate		= ses_set_locate,
	.set_active		= ses_set_active,
};

struct ses_host_edev {
	struct Scsi_Host *shost;
	struct enclosure_device *edev;
};

int ses_match_host(struct enclosure_device *edev, void *data)
{
	struct ses_host_edev *sed = data;
	struct scsi_device *sdev;

	if (!scsi_is_sdev_device(edev->edev.parent))
		return 0;

	sdev = to_scsi_device(edev->edev.parent);

	if (sdev->host != sed->shost)
		return 0;

	sed->edev = edev;
	return 1;
}

static void ses_process_descriptor(struct enclosure_component *ecomp,
				   unsigned char *desc)
{
	int eip = desc[0] & 0x10;
	int invalid = desc[0] & 0x80;
	enum scsi_protocol proto = desc[0] & 0x0f;
	u64 addr = 0;
	struct ses_component *scomp = ecomp->scratch;
	unsigned char *d;

	scomp->desc = desc;

	if (invalid)
		return;

	switch (proto) {
	case SCSI_PROTOCOL_SAS:
		if (eip)
			d = desc + 8;
		else
			d = desc + 4;
		/* only take the phy0 addr */
		addr = (u64)d[12] << 56 |
			(u64)d[13] << 48 |
			(u64)d[14] << 40 |
			(u64)d[15] << 32 |
			(u64)d[16] << 24 |
			(u64)d[17] << 16 |
			(u64)d[18] << 8 |
			(u64)d[19];
		break;
	default:
		/* FIXME: Need to add more protocols than just SAS */
		break;
	}
	scomp->addr = addr;
}

struct efd {
	u64 addr;
	struct device *dev;
};

static int ses_enclosure_find_by_addr(struct enclosure_device *edev,
				      void *data)
{
	struct efd *efd = data;
	int i;
	struct ses_component *scomp;

	if (!edev->component[0].scratch)
		return 0;

	for (i = 0; i < edev->components; i++) {
		scomp = edev->component[i].scratch;
		if (scomp->addr != efd->addr)
			continue;

		enclosure_add_device(edev, i, efd->dev);
		return 1;
	}
	return 0;
}

#define VPD_INQUIRY_SIZE 36

static void ses_match_to_enclosure(struct enclosure_device *edev,
				   struct scsi_device *sdev)
{
	unsigned char *buf = kmalloc(VPD_INQUIRY_SIZE, GFP_KERNEL);
	unsigned char *desc;
	u16 vpd_len;
	struct efd efd = {
		.addr = 0,
	};
	unsigned char cmd[] = {
		INQUIRY,
		1,
		0x83,
		VPD_INQUIRY_SIZE >> 8,
		VPD_INQUIRY_SIZE & 0xff,
		0
	};

	if (!buf)
		return;

	if (scsi_execute_req(sdev, cmd, DMA_FROM_DEVICE, buf,
			     VPD_INQUIRY_SIZE, NULL, SES_TIMEOUT, SES_RETRIES))
		goto free;

	vpd_len = (buf[2] << 8) + buf[3];
	kfree(buf);
	buf = kmalloc(vpd_len, GFP_KERNEL);
	if (!buf)
		return;
	cmd[3] = vpd_len >> 8;
	cmd[4] = vpd_len & 0xff;
	if (scsi_execute_req(sdev, cmd, DMA_FROM_DEVICE, buf,
			     vpd_len, NULL, SES_TIMEOUT, SES_RETRIES))
		goto free;

	desc = buf + 4;
	while (desc < buf + vpd_len) {
		enum scsi_protocol proto = desc[0] >> 4;
		u8 code_set = desc[0] & 0x0f;
		u8 piv = desc[1] & 0x80;
		u8 assoc = (desc[1] & 0x30) >> 4;
		u8 type = desc[1] & 0x0f;
		u8 len = desc[3];

		if (piv && code_set == 1 && assoc == 1 && code_set == 1
		    && proto == SCSI_PROTOCOL_SAS && type == 3 && len == 8)
			efd.addr = (u64)desc[4] << 56 |
				(u64)desc[5] << 48 |
				(u64)desc[6] << 40 |
				(u64)desc[7] << 32 |
				(u64)desc[8] << 24 |
				(u64)desc[9] << 16 |
				(u64)desc[10] << 8 |
				(u64)desc[11];

		desc += len + 4;
	}
	if (!efd.addr)
		goto free;

	efd.dev = &sdev->sdev_gendev;

	enclosure_for_each_device(ses_enclosure_find_by_addr, &efd);
 free:
	kfree(buf);
}

#define INIT_ALLOC_SIZE 32

static int ses_intf_add(struct device *cdev,
			struct class_interface *intf)
{
	struct scsi_device *sdev = to_scsi_device(cdev->parent);
	struct scsi_device *tmp_sdev;
	unsigned char *buf = NULL, *hdr_buf, *type_ptr, *desc_ptr = NULL,
		*addl_desc_ptr = NULL;
	struct ses_device *ses_dev;
	u32 result;
	int i, j, types, len, page7_len = 0, components = 0;
	int err = -ENOMEM;
	struct enclosure_device *edev;
	struct ses_component *scomp = NULL;

	if (!scsi_device_enclosure(sdev)) {
		/* not an enclosure, but might be in one */
		edev = enclosure_find(&sdev->host->shost_gendev);
		if (edev) {
			ses_match_to_enclosure(edev, sdev);
			put_device(&edev->edev);
		}
		return -ENODEV;
	}

	/* TYPE_ENCLOSURE prints a message in probe */
	if (sdev->type != TYPE_ENCLOSURE)
		sdev_printk(KERN_NOTICE, sdev, "Embedded Enclosure Device\n");

	ses_dev = kzalloc(sizeof(*ses_dev), GFP_KERNEL);
	hdr_buf = kzalloc(INIT_ALLOC_SIZE, GFP_KERNEL);
	if (!hdr_buf || !ses_dev)
		goto err_init_free;

	result = ses_recv_diag(sdev, 1, hdr_buf, INIT_ALLOC_SIZE);
	if (result)
		goto recv_failed;

	if (hdr_buf[1] != 0) {
		/* FIXME: need subenclosure support; I've just never
		 * seen a device with subenclosures and it makes the
		 * traversal routines more complex */
		sdev_printk(KERN_ERR, sdev,
			"FIXME driver has no support for subenclosures (%d)\n",
			hdr_buf[1]);
		goto err_free;
	}

	len = (hdr_buf[2] << 8) + hdr_buf[3] + 4;
	buf = kzalloc(len, GFP_KERNEL);
	if (!buf)
		goto err_free;

	result = ses_recv_diag(sdev, 1, buf, len);
	if (result)
		goto recv_failed;

	types = buf[10];

	type_ptr = buf + 12 + buf[11];

	for (i = 0; i < types; i++, type_ptr += 4) {
		if (type_ptr[0] == ENCLOSURE_COMPONENT_DEVICE ||
		    type_ptr[0] == ENCLOSURE_COMPONENT_ARRAY_DEVICE)
			components += type_ptr[1];
	}
	ses_dev->page1 = buf;
	ses_dev->page1_len = len;
	buf = NULL;

	result = ses_recv_diag(sdev, 2, hdr_buf, INIT_ALLOC_SIZE);
	if (result)
		goto recv_failed;

	len = (hdr_buf[2] << 8) + hdr_buf[3] + 4;
	buf = kzalloc(len, GFP_KERNEL);
	if (!buf)
		goto err_free;

	/* make sure getting page 2 actually works */
	result = ses_recv_diag(sdev, 2, buf, len);
	if (result)
		goto recv_failed;
	ses_dev->page2 = buf;
	ses_dev->page2_len = len;
	buf = NULL;

	/* The additional information page --- allows us
	 * to match up the devices */
	result = ses_recv_diag(sdev, 10, hdr_buf, INIT_ALLOC_SIZE);
	if (!result) {

		len = (hdr_buf[2] << 8) + hdr_buf[3] + 4;
		buf = kzalloc(len, GFP_KERNEL);
		if (!buf)
			goto err_free;

		result = ses_recv_diag(sdev, 10, buf, len);
		if (result)
			goto recv_failed;
		ses_dev->page10 = buf;
		ses_dev->page10_len = len;
		buf = NULL;
	}

	scomp = kzalloc(sizeof(struct ses_component) * components, GFP_KERNEL);
	if (!scomp)
		goto err_free;

	edev = enclosure_register(cdev->parent, sdev->sdev_gendev.bus_id,
				  components, &ses_enclosure_callbacks);
	if (IS_ERR(edev)) {
		err = PTR_ERR(edev);
		goto err_free;
	}

	edev->scratch = ses_dev;
	for (i = 0; i < components; i++)
		edev->component[i].scratch = scomp + i;

	/* Page 7 for the descriptors is optional */
	result = ses_recv_diag(sdev, 7, hdr_buf, INIT_ALLOC_SIZE);
	if (result)
		goto simple_populate;

	page7_len = len = (hdr_buf[2] << 8) + hdr_buf[3] + 4;
	/* add 1 for trailing '\0' we'll use */
	buf = kzalloc(len + 1, GFP_KERNEL);
	if (!buf)
		goto simple_populate;
	result = ses_recv_diag(sdev, 7, buf, len);
	if (result) {
 simple_populate:
		kfree(buf);
		buf = NULL;
		desc_ptr = NULL;
		addl_desc_ptr = NULL;
	} else {
		desc_ptr = buf + 8;
		len = (desc_ptr[2] << 8) + desc_ptr[3];
		/* skip past overall descriptor */
		desc_ptr += len + 4;
		if (ses_dev->page10)
			addl_desc_ptr = ses_dev->page10 + 8;
	}
	type_ptr = ses_dev->page1 + 12 + ses_dev->page1[11];
	components = 0;
	for (i = 0; i < types; i++, type_ptr += 4) {
		for (j = 0; j < type_ptr[1]; j++) {
			char *name = NULL;
			struct enclosure_component *ecomp;

			if (desc_ptr) {
				if (desc_ptr >= buf + page7_len) {
					desc_ptr = NULL;
				} else {
					len = (desc_ptr[2] << 8) + desc_ptr[3];
					desc_ptr += 4;
					/* Add trailing zero - pushes into
					 * reserved space */
					desc_ptr[len] = '\0';
					name = desc_ptr;
				}
			}
			if (type_ptr[0] == ENCLOSURE_COMPONENT_DEVICE ||
			    type_ptr[0] == ENCLOSURE_COMPONENT_ARRAY_DEVICE) {

				ecomp =	enclosure_component_register(edev,
							     components++,
							     type_ptr[0],
							     name);

				if (!IS_ERR(ecomp) && addl_desc_ptr)
					ses_process_descriptor(ecomp,
							       addl_desc_ptr);
			}
			if (desc_ptr)
				desc_ptr += len;

			if (addl_desc_ptr)
				addl_desc_ptr += addl_desc_ptr[1] + 2;

		}
	}
	kfree(buf);
	kfree(hdr_buf);

	/* see if there are any devices matching before
	 * we found the enclosure */
	shost_for_each_device(tmp_sdev, sdev->host) {
		if (tmp_sdev->lun != 0 || scsi_device_enclosure(tmp_sdev))
			continue;
		ses_match_to_enclosure(edev, tmp_sdev);
	}

	return 0;

 recv_failed:
	sdev_printk(KERN_ERR, sdev, "Failed to get diagnostic page 0x%x\n",
		    result);
	err = -ENODEV;
 err_free:
	kfree(buf);
	kfree(scomp);
	kfree(ses_dev->page10);
	kfree(ses_dev->page2);
	kfree(ses_dev->page1);
 err_init_free:
	kfree(ses_dev);
	kfree(hdr_buf);
	sdev_printk(KERN_ERR, sdev, "Failed to bind enclosure %d\n", err);
	return err;
}

static int ses_remove(struct device *dev)
{
	return 0;
}

static void ses_intf_remove(struct device *cdev,
			    struct class_interface *intf)
{
	struct scsi_device *sdev = to_scsi_device(cdev->parent);
	struct enclosure_device *edev;
	struct ses_device *ses_dev;

	if (!scsi_device_enclosure(sdev))
		return;

	edev = enclosure_find(cdev->parent);
	if (!edev)
		return;

	ses_dev = edev->scratch;
	edev->scratch = NULL;

	kfree(ses_dev->page10);
	kfree(ses_dev->page1);
	kfree(ses_dev->page2);
	kfree(ses_dev);

	kfree(edev->component[0].scratch);

	put_device(&edev->edev);
	enclosure_unregister(edev);
}

static struct class_interface ses_interface = {
	.add_dev	= ses_intf_add,
	.remove_dev	= ses_intf_remove,
};

static struct scsi_driver ses_template = {
	.owner			= THIS_MODULE,
	.gendrv = {
		.name		= "ses",
		.probe		= ses_probe,
		.remove		= ses_remove,
	},
};

static int __init ses_init(void)
{
	int err;

	err = scsi_register_interface(&ses_interface);
	if (err)
		return err;

	err = scsi_register_driver(&ses_template.gendrv);
	if (err)
		goto out_unreg;

	return 0;

 out_unreg:
	scsi_unregister_interface(&ses_interface);
	return err;
}

static void __exit ses_exit(void)
{
	scsi_unregister_driver(&ses_template.gendrv);
	scsi_unregister_interface(&ses_interface);
}

module_init(ses_init);
module_exit(ses_exit);

MODULE_ALIAS_SCSI_DEVICE(TYPE_ENCLOSURE);

MODULE_AUTHOR("James Bottomley");
MODULE_DESCRIPTION("SCSI Enclosure Services (ses) driver");
MODULE_LICENSE("GPL v2");
w_nodes, maxnode); if (err) return err; /* Find the mm_struct */ read_lock(&tasklist_lock); task = pid ? find_task_by_pid(pid) : current; if (!task) { read_unlock(&tasklist_lock); return -ESRCH; } mm = get_task_mm(task); read_unlock(&tasklist_lock); if (!mm) return -EINVAL; /* * Check if this process has the right to modify the specified * process. The right exists if the process has administrative * capabilities, superuser privileges or the same * userid as the target process. */ if ((current->euid != task->suid) && (current->euid != task->uid) && (current->uid != task->suid) && (current->uid != task->uid) && !capable(CAP_SYS_NICE)) { err = -EPERM; goto out; } task_nodes = cpuset_mems_allowed(task); /* Is the user allowed to access the target nodes? */ if (!nodes_subset(new, task_nodes) && !capable(CAP_SYS_NICE)) { err = -EPERM; goto out; } if (!nodes_subset(new, node_online_map)) { err = -EINVAL; goto out; } err = security_task_movememory(task); if (err) goto out; err = do_migrate_pages(mm, &old, &new, capable(CAP_SYS_NICE) ? MPOL_MF_MOVE_ALL : MPOL_MF_MOVE); out: mmput(mm); return err; } /* Retrieve NUMA policy */ asmlinkage long sys_get_mempolicy(int __user *policy, unsigned long __user *nmask, unsigned long maxnode, unsigned long addr, unsigned long flags) { int err, pval; nodemask_t nodes; if (nmask != NULL && maxnode < MAX_NUMNODES) return -EINVAL; err = do_get_mempolicy(&pval, &nodes, addr, flags); if (err) return err; if (policy && put_user(pval, policy)) return -EFAULT; if (nmask) err = copy_nodes_to_user(nmask, maxnode, &nodes); return err; } #ifdef CONFIG_COMPAT asmlinkage long compat_sys_get_mempolicy(int __user *policy, compat_ulong_t __user *nmask, compat_ulong_t maxnode, compat_ulong_t addr, compat_ulong_t flags) { long err; unsigned long __user *nm = NULL; unsigned long nr_bits, alloc_size; DECLARE_BITMAP(bm, MAX_NUMNODES); nr_bits = min_t(unsigned long, maxnode-1, MAX_NUMNODES); alloc_size = ALIGN(nr_bits, BITS_PER_LONG) / 8; if (nmask) nm = compat_alloc_user_space(alloc_size); err = sys_get_mempolicy(policy, nm, nr_bits+1, addr, flags); if (!err && nmask) { err = copy_from_user(bm, nm, alloc_size); /* ensure entire bitmap is zeroed */ err |= clear_user(nmask, ALIGN(maxnode-1, 8) / 8); err |= compat_put_bitmap(nmask, bm, nr_bits); } return err; } asmlinkage long compat_sys_set_mempolicy(int mode, compat_ulong_t __user *nmask, compat_ulong_t maxnode) { long err = 0; unsigned long __user *nm = NULL; unsigned long nr_bits, alloc_size; DECLARE_BITMAP(bm, MAX_NUMNODES); nr_bits = min_t(unsigned long, maxnode-1, MAX_NUMNODES); alloc_size = ALIGN(nr_bits, BITS_PER_LONG) / 8; if (nmask) { err = compat_get_bitmap(bm, nmask, nr_bits); nm = compat_alloc_user_space(alloc_size); err |= copy_to_user(nm, bm, alloc_size); } if (err) return -EFAULT; return sys_set_mempolicy(mode, nm, nr_bits+1); } asmlinkage long compat_sys_mbind(compat_ulong_t start, compat_ulong_t len, compat_ulong_t mode, compat_ulong_t __user *nmask, compat_ulong_t maxnode, compat_ulong_t flags) { long err = 0; unsigned long __user *nm = NULL; unsigned long nr_bits, alloc_size; nodemask_t bm; nr_bits = min_t(unsigned long, maxnode-1, MAX_NUMNODES); alloc_size = ALIGN(nr_bits, BITS_PER_LONG) / 8; if (nmask) { err = compat_get_bitmap(nodes_addr(bm), nmask, nr_bits); nm = compat_alloc_user_space(alloc_size); err |= copy_to_user(nm, nodes_addr(bm), alloc_size); } if (err) return -EFAULT; return sys_mbind(start, len, mode, nm, nr_bits+1, flags); } #endif /* Return effective policy for a VMA */ static struct mempolicy * get_vma_policy(struct task_struct *task, struct vm_area_struct *vma, unsigned long addr) { struct mempolicy *pol = task->mempolicy; if (vma) { if (vma->vm_ops && vma->vm_ops->get_policy) pol = vma->vm_ops->get_policy(vma, addr); else if (vma->vm_policy && vma->vm_policy->policy != MPOL_DEFAULT) pol = vma->vm_policy; } if (!pol) pol = &default_policy; return pol; } /* Return a zonelist representing a mempolicy */ static struct zonelist *zonelist_policy(gfp_t gfp, struct mempolicy *policy) { int nd; switch (policy->policy) { case MPOL_PREFERRED: nd = policy->v.preferred_node; if (nd < 0) nd = numa_node_id(); break; case MPOL_BIND: /* Lower zones don't get a policy applied */ /* Careful: current->mems_allowed might have moved */ if (gfp_zone(gfp) >= policy_zone) if (cpuset_zonelist_valid_mems_allowed(policy->v.zonelist)) return policy->v.zonelist; /*FALL THROUGH*/ case MPOL_INTERLEAVE: /* should not happen */ case MPOL_DEFAULT: nd = numa_node_id(); break; default: nd = 0; BUG(); } return NODE_DATA(nd)->node_zonelists + gfp_zone(gfp); } /* Do dynamic interleaving for a process */ static unsigned interleave_nodes(struct mempolicy *policy) { unsigned nid, next; struct task_struct *me = current; nid = me->il_next; next = next_node(nid, policy->v.nodes); if (next >= MAX_NUMNODES) next = first_node(policy->v.nodes); me->il_next = next; return nid; } /* * Depending on the memory policy provide a node from which to allocate the * next slab entry. */ unsigned slab_node(struct mempolicy *policy) { int pol = policy ? policy->policy : MPOL_DEFAULT; switch (pol) { case MPOL_INTERLEAVE: return interleave_nodes(policy); case MPOL_BIND: /* * Follow bind policy behavior and start allocation at the * first node. */ return zone_to_nid(policy->v.zonelist->zones[0]); case MPOL_PREFERRED: if (policy->v.preferred_node >= 0) return policy->v.preferred_node; /* Fall through */ default: return numa_node_id(); } } /* Do static interleaving for a VMA with known offset. */ static unsigned offset_il_node(struct mempolicy *pol, struct vm_area_struct *vma, unsigned long off) { unsigned nnodes = nodes_weight(pol->v.nodes); unsigned target = (unsigned)off % nnodes; int c; int nid = -1; c = 0; do { nid = next_node(nid, pol->v.nodes); c++; } while (c <= target); return nid; } /* Determine a node number for interleave */ static inline unsigned interleave_nid(struct mempolicy *pol, struct vm_area_struct *vma, unsigned long addr, int shift) { if (vma) { unsigned long off; /* * for small pages, there is no difference between * shift and PAGE_SHIFT, so the bit-shift is safe. * for huge pages, since vm_pgoff is in units of small * pages, we need to shift off the always 0 bits to get * a useful offset. */ BUG_ON(shift < PAGE_SHIFT); off = vma->vm_pgoff >> (shift - PAGE_SHIFT); off += (addr - vma->vm_start) >> shift; return offset_il_node(pol, vma, off); } else return interleave_nodes(pol); } #ifdef CONFIG_HUGETLBFS /* Return a zonelist suitable for a huge page allocation. */ struct zonelist *huge_zonelist(struct vm_area_struct *vma, unsigned long addr, gfp_t gfp_flags) { struct mempolicy *pol = get_vma_policy(current, vma, addr); if (pol->policy == MPOL_INTERLEAVE) { unsigned nid; nid = interleave_nid(pol, vma, addr, HPAGE_SHIFT); return NODE_DATA(nid)->node_zonelists + gfp_zone(gfp_flags); } return zonelist_policy(GFP_HIGHUSER, pol); } #endif /* Allocate a page in interleaved policy. Own path because it needs to do special accounting. */ static struct page *alloc_page_interleave(gfp_t gfp, unsigned order, unsigned nid) { struct zonelist *zl; struct page *page; zl = NODE_DATA(nid)->node_zonelists + gfp_zone(gfp); page = __alloc_pages(gfp, order, zl); if (page && page_zone(page) == zl->zones[0]) inc_zone_page_state(page, NUMA_INTERLEAVE_HIT); return page; } /** * alloc_page_vma - Allocate a page for a VMA. * * @gfp: * %GFP_USER user allocation. * %GFP_KERNEL kernel allocations, * %GFP_HIGHMEM highmem/user allocations, * %GFP_FS allocation should not call back into a file system. * %GFP_ATOMIC don't sleep. * * @vma: Pointer to VMA or NULL if not available. * @addr: Virtual Address of the allocation. Must be inside the VMA. * * This function allocates a page from the kernel page pool and applies * a NUMA policy associated with the VMA or the current process. * When VMA is not NULL caller must hold down_read on the mmap_sem of the * mm_struct of the VMA to prevent it from going away. Should be used for * all allocations for pages that will be mapped into * user space. Returns NULL when no page can be allocated. * * Should be called with the mm_sem of the vma hold. */ struct page * alloc_page_vma(gfp_t gfp, struct vm_area_struct *vma, unsigned long addr) { struct mempolicy *pol = get_vma_policy(current, vma, addr); cpuset_update_task_memory_state(); if (unlikely(pol->policy == MPOL_INTERLEAVE)) { unsigned nid; nid = interleave_nid(pol, vma, addr, PAGE_SHIFT); return alloc_page_interleave(gfp, 0, nid); } return __alloc_pages(gfp, 0, zonelist_policy(gfp, pol)); } /** * alloc_pages_current - Allocate pages. * * @gfp: * %GFP_USER user allocation, * %GFP_KERNEL kernel allocation, * %GFP_HIGHMEM highmem allocation, * %GFP_FS don't call back into a file system. * %GFP_ATOMIC don't sleep. * @order: Power of two of allocation size in pages. 0 is a single page. * * Allocate a page from the kernel page pool. When not in * interrupt context and apply the current process NUMA policy. * Returns NULL when no page can be allocated. * * Don't call cpuset_update_task_memory_state() unless * 1) it's ok to take cpuset_sem (can WAIT), and * 2) allocating for current task (not interrupt). */ struct page *alloc_pages_current(gfp_t gfp, unsigned order) { struct mempolicy *pol = current->mempolicy; if ((gfp & __GFP_WAIT) && !in_interrupt()) cpuset_update_task_memory_state(); if (!pol || in_interrupt() || (gfp & __GFP_THISNODE)) pol = &default_policy; if (pol->policy == MPOL_INTERLEAVE) return alloc_page_interleave(gfp, order, interleave_nodes(pol)); return __alloc_pages(gfp, order, zonelist_policy(gfp, pol)); } EXPORT_SYMBOL(alloc_pages_current); /* * If mpol_copy() sees current->cpuset == cpuset_being_rebound, then it * rebinds the mempolicy its copying by calling mpol_rebind_policy() * with the mems_allowed returned by cpuset_mems_allowed(). This * keeps mempolicies cpuset relative after its cpuset moves. See * further kernel/cpuset.c update_nodemask(). */ void *cpuset_being_rebound; /* Slow path of a mempolicy copy */ struct mempolicy *__mpol_copy(struct mempolicy *old) { struct mempolicy *new = kmem_cache_alloc(policy_cache, GFP_KERNEL); if (!new) return ERR_PTR(-ENOMEM); if (current_cpuset_is_being_rebound()) { nodemask_t mems = cpuset_mems_allowed(current); mpol_rebind_policy(old, &mems); } *new = *old; atomic_set(&new->refcnt, 1); if (new->policy == MPOL_BIND) { int sz = ksize(old->v.zonelist); new->v.zonelist = kmemdup(old->v.zonelist, sz, GFP_KERNEL); if (!new->v.zonelist) { kmem_cache_free(policy_cache, new); return ERR_PTR(-ENOMEM); } } return new; } /* Slow path of a mempolicy comparison */ int __mpol_equal(struct mempolicy *a, struct mempolicy *b) { if (!a || !b) return 0; if (a->policy != b->policy) return 0; switch (a->policy) { case MPOL_DEFAULT: return 1; case MPOL_INTERLEAVE: return nodes_equal(a->v.nodes, b->v.nodes); case MPOL_PREFERRED: return a->v.preferred_node == b->v.preferred_node; case MPOL_BIND: { int i; for (i = 0; a->v.zonelist->zones[i]; i++) if (a->v.zonelist->zones[i] != b->v.zonelist->zones[i]) return 0; return b->v.zonelist->zones[i] == NULL; } default: BUG(); return 0; } } /* Slow path of a mpol destructor. */ void __mpol_free(struct mempolicy *p) { if (!atomic_dec_and_test(&p->refcnt)) return; if (p->policy == MPOL_BIND) kfree(p->v.zonelist); p->policy = MPOL_DEFAULT; kmem_cache_free(policy_cache, p); } /* * Shared memory backing store policy support. * * Remember policies even when nobody has shared memory mapped. * The policies are kept in Red-Black tree linked from the inode. * They are protected by the sp->lock spinlock, which should be held * for any accesses to the tree. */ /* lookup first element intersecting start-end */ /* Caller holds sp->lock */ static struct sp_node * sp_lookup(struct shared_policy *sp, unsigned long start, unsigned long end) { struct rb_node *n = sp->root.rb_node; while (n) { struct sp_node *p = rb_entry(n, struct sp_node, nd); if (start >= p->end) n = n->rb_right; else if (end <= p->start) n = n->rb_left; else break; } if (!n) return NULL; for (;;) { struct sp_node *w = NULL; struct rb_node *prev = rb_prev(n); if (!prev) break; w = rb_entry(prev, struct sp_node, nd); if (w->end <= start) break; n = prev; } return rb_entry(n, struct sp_node, nd); } /* Insert a new shared policy into the list. */ /* Caller holds sp->lock */ static void sp_insert(struct shared_policy *sp, struct sp_node *new) { struct rb_node **p = &sp->root.rb_node; struct rb_node *parent = NULL; struct sp_node *nd; while (*p) { parent = *p; nd = rb_entry(parent, struct sp_node, nd); if (new->start < nd->start) p = &(*p)->rb_left; else if (new->end > nd->end) p = &(*p)->rb_right; else BUG(); } rb_link_node(&new->nd, parent, p); rb_insert_color(&new->nd, &sp->root); pr_debug("inserting %lx-%lx: %d\n", new->start, new->end, new->policy ? new->policy->policy : 0); } /* Find shared policy intersecting idx */ struct mempolicy * mpol_shared_policy_lookup(struct shared_policy *sp, unsigned long idx) { struct mempolicy *pol = NULL; struct sp_node *sn; if (!sp->root.rb_node) return NULL; spin_lock(&sp->lock); sn = sp_lookup(sp, idx, idx+1); if (sn) { mpol_get(sn->policy); pol = sn->policy; } spin_unlock(&sp->lock); return pol; } static void sp_delete(struct shared_policy *sp, struct sp_node *n) { pr_debug("deleting %lx-l%lx\n", n->start, n->end); rb_erase(&n->nd, &sp->root); mpol_free(n->policy); kmem_cache_free(sn_cache, n); } struct sp_node * sp_alloc(unsigned long start, unsigned long end, struct mempolicy *pol) { struct sp_node *n = kmem_cache_alloc(sn_cache, GFP_KERNEL); if (!n) return NULL; n->start = start; n->end = end; mpol_get(pol); n->policy = pol; return n; } /* Replace a policy range. */ static int shared_policy_replace(struct shared_policy *sp, unsigned long start, unsigned long end, struct sp_node *new) { struct sp_node *n, *new2 = NULL; restart: spin_lock(&sp->lock); n = sp_lookup(sp, start, end); /* Take care of old policies in the same range. */ while (n && n->start < end) { struct rb_node *next = rb_next(&n->nd); if (n->start >= start) { if (n->end <= end) sp_delete(sp, n); else n->start = end; } else { /* Old policy spanning whole new range. */ if (n->end > end) { if (!new2) { spin_unlock(&sp->lock); new2 = sp_alloc(end, n->end, n->policy); if (!new2) return -ENOMEM; goto restart; } n->end = start; sp_insert(sp, new2); new2 = NULL; break; } else n->end = start; } if (!next) break; n = rb_entry(next, struct sp_node, nd); } if (new) sp_insert(sp, new); spin_unlock(&sp->lock); if (new2) { mpol_free(new2->policy); kmem_cache_free(sn_cache, new2); } return 0; } void mpol_shared_policy_init(struct shared_policy *info, int policy, nodemask_t *policy_nodes) { info->root = RB_ROOT; spin_lock_init(&info->lock); if (policy != MPOL_DEFAULT) { struct mempolicy *newpol; /* Falls back to MPOL_DEFAULT on any error */ newpol = mpol_new(policy, policy_nodes); if (!IS_ERR(newpol)) { /* Create pseudo-vma that contains just the policy */ struct vm_area_struct pvma; memset(&pvma, 0, sizeof(struct vm_area_struct)); /* Policy covers entire file */ pvma.vm_end = TASK_SIZE; mpol_set_shared_policy(info, &pvma, newpol); mpol_free(newpol); } } } int mpol_set_shared_policy(struct shared_policy *info, struct vm_area_struct *vma, struct mempolicy *npol) { int err; struct sp_node *new = NULL; unsigned long sz = vma_pages(vma); pr_debug("set_shared_policy %lx sz %lu %d %lx\n", vma->vm_pgoff, sz, npol? npol->policy : -1, npol ? nodes_addr(npol->v.nodes)[0] : -1); if (npol) { new = sp_alloc(vma->vm_pgoff, vma->vm_pgoff + sz, npol); if (!new) return -ENOMEM; } err = shared_policy_replace(info, vma->vm_pgoff, vma->vm_pgoff+sz, new); if (err && new) kmem_cache_free(sn_cache, new); return err; } /* Free a backing policy store on inode delete. */ void mpol_free_shared_policy(struct shared_policy *p) { struct sp_node *n; struct rb_node *next; if (!p->root.rb_node) return; spin_lock(&p->lock); next = rb_first(&p->root); while (next) { n = rb_entry(next, struct sp_node, nd); next = rb_next(&n->nd); rb_erase(&n->nd, &p->root); mpol_free(n->policy); kmem_cache_free(sn_cache, n); } spin_unlock(&p->lock); } /* assumes fs == KERNEL_DS */ void __init numa_policy_init(void) { nodemask_t interleave_nodes; unsigned long largest = 0; int nid, prefer = 0; policy_cache = kmem_cache_create("numa_policy", sizeof(struct mempolicy), 0, SLAB_PANIC, NULL); sn_cache = kmem_cache_create("shared_policy_node", sizeof(struct sp_node), 0, SLAB_PANIC, NULL); /* * Set interleaving policy for system init. Interleaving is only * enabled across suitably sized nodes (default is >= 16MB), or * fall back to the largest node if they're all smaller. */ nodes_clear(interleave_nodes); for_each_online_node(nid) { unsigned long total_pages = node_present_pages(nid); /* Preserve the largest node */ if (largest < total_pages) { largest = total_pages; prefer = nid; } /* Interleave this node? */ if ((total_pages << PAGE_SHIFT) >= (16 << 20)) node_set(nid, interleave_nodes); } /* All too small, use the largest */ if (unlikely(nodes_empty(interleave_nodes))) node_set(prefer, interleave_nodes); if (do_set_mempolicy(MPOL_INTERLEAVE, &interleave_nodes)) printk("numa_policy_init: interleaving failed\n"); } /* Reset policy of current process to default */ void numa_default_policy(void) { do_set_mempolicy(MPOL_DEFAULT, NULL); } /* Migrate a policy to a different set of nodes */ void mpol_rebind_policy(struct mempolicy *pol, const nodemask_t *newmask) { nodemask_t *mpolmask; nodemask_t tmp; if (!pol) return; mpolmask = &pol->cpuset_mems_allowed; if (nodes_equal(*mpolmask, *newmask)) return; switch (pol->policy) { case MPOL_DEFAULT: break; case MPOL_INTERLEAVE: nodes_remap(tmp, pol->v.nodes, *mpolmask, *newmask); pol->v.nodes = tmp; *mpolmask = *newmask; current->il_next = node_remap(current->il_next, *mpolmask, *newmask); break; case MPOL_PREFERRED: pol->v.preferred_node = node_remap(pol->v.preferred_node, *mpolmask, *newmask); *mpolmask = *newmask; break; case MPOL_BIND: { nodemask_t nodes; struct zone **z; struct zonelist *zonelist; nodes_clear(nodes); for (z = pol->v.zonelist->zones; *z; z++) node_set(zone_to_nid(*z), nodes); nodes_remap(tmp, nodes, *mpolmask, *newmask); nodes = tmp; zonelist = bind_zonelist(&nodes); /* If no mem, then zonelist is NULL and we keep old zonelist. * If that old zonelist has no remaining mems_allowed nodes, * then zonelist_policy() will "FALL THROUGH" to MPOL_DEFAULT. */ if (!IS_ERR(zonelist)) { /* Good - got mem - substitute new zonelist */ kfree(pol->v.zonelist); pol->v.zonelist = zonelist; } *mpolmask = *newmask; break; } default: BUG(); break; } } /* * Wrapper for mpol_rebind_policy() that just requires task * pointer, and updates task mempolicy. */ void mpol_rebind_task(struct task_struct *tsk, const nodemask_t *new) { mpol_rebind_policy(tsk->mempolicy, new); } /* * Rebind each vma in mm to new nodemask. * * Call holding a reference to mm. Takes mm->mmap_sem during call. */ void mpol_rebind_mm(struct mm_struct *mm, nodemask_t *new) { struct vm_area_struct *vma; down_write(&mm->mmap_sem); for (vma = mm->mmap; vma; vma = vma->vm_next) mpol_rebind_policy(vma->vm_policy, new); up_write(&mm->mmap_sem); } /* * Display pages allocated per node and memory policy via /proc. */ static const char * const policy_types[] = { "default", "prefer", "bind", "interleave" }; /* * Convert a mempolicy into a string. * Returns the number of characters in buffer (if positive) * or an error (negative) */ static inline int mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol) { char *p = buffer; int l; nodemask_t nodes; int mode = pol ? pol->policy : MPOL_DEFAULT; switch (mode) { case MPOL_DEFAULT: nodes_clear(nodes); break; case MPOL_PREFERRED: nodes_clear(nodes); node_set(pol->v.preferred_node, nodes); break; case MPOL_BIND: get_zonemask(pol, &nodes); break; case MPOL_INTERLEAVE: nodes = pol->v.nodes; break; default: BUG(); return -EFAULT; } l = strlen(policy_types[mode]); if (buffer + maxlen < p + l + 1) return -ENOSPC; strcpy(p, policy_types[mode]); p += l; if (!nodes_empty(nodes)) { if (buffer + maxlen < p + 2) return -ENOSPC; *p++ = '='; p += nodelist_scnprintf(p, buffer + maxlen - p, nodes); } return p - buffer; } struct numa_maps { unsigned long pages; unsigned long anon; unsigned long active; unsigned long writeback; unsigned long mapcount_max; unsigned long dirty; unsigned long swapcache; unsigned long node[MAX_NUMNODES]; }; static void gather_stats(struct page *page, void *private, int pte_dirty) { struct numa_maps *md = private; int count = page_mapcount(page); md->pages++; if (pte_dirty || PageDirty(page)) md->dirty++; if (PageSwapCache(page)) md->swapcache++; if (PageActive(page)) md->active++; if (PageWriteback(page)) md->writeback++; if (PageAnon(page)) md->anon++; if (count > md->mapcount_max) md->mapcount_max = count; md->node[page_to_nid(page)]++; } #ifdef CONFIG_HUGETLB_PAGE static void check_huge_range(struct vm_area_struct *vma, unsigned long start, unsigned long end, struct numa_maps *md) { unsigned long addr; struct page *page; for (addr = start; addr < end; addr += HPAGE_SIZE) { pte_t *ptep = huge_pte_offset(vma->vm_mm, addr & HPAGE_MASK); pte_t pte; if (!ptep) continue; pte = *ptep; if (pte_none(pte)) continue; page = pte_page(pte); if (!page) continue; gather_stats(page, md, pte_dirty(*ptep)); } } #else static inline void check_huge_range(struct vm_area_struct *vma, unsigned long start, unsigned long end, struct numa_maps *md) { } #endif int show_numa_map(struct seq_file *m, void *v) { struct proc_maps_private *priv = m->private; struct vm_area_struct *vma = v; struct numa_maps *md; struct file *file = vma->vm_file; struct mm_struct *mm = vma->vm_mm; int n; char buffer[50]; if (!mm) return 0; md = kzalloc(sizeof(struct numa_maps), GFP_KERNEL); if (!md) return 0; mpol_to_str(buffer, sizeof(buffer), get_vma_policy(priv->task, vma, vma->vm_start)); seq_printf(m, "%08lx %s", vma->vm_start, buffer); if (file) { seq_printf(m, " file="); seq_path(m, file->f_path.mnt, file->f_path.dentry, "\n\t= "); } else if (vma->vm_start <= mm->brk && vma->vm_end >= mm->start_brk) { seq_printf(m, " heap"); } else if (vma->vm_start <= mm->start_stack && vma->vm_end >= mm->start_stack) { seq_printf(m, " stack"); } if (is_vm_hugetlb_page(vma)) { check_huge_range(vma, vma->vm_start, vma->vm_end, md); seq_printf(m, " huge"); } else { check_pgd_range(vma, vma->vm_start, vma->vm_end, &node_online_map, MPOL_MF_STATS, md); } if (!md->pages) goto out; if (md->anon) seq_printf(m," anon=%lu",md->anon); if (md->dirty) seq_printf(m," dirty=%lu",md->dirty); if (md->pages != md->anon && md->pages != md->dirty) seq_printf(m, " mapped=%lu", md->pages); if (md->mapcount_max > 1) seq_printf(m, " mapmax=%lu", md->mapcount_max); if (md->swapcache) seq_printf(m," swapcache=%lu", md->swapcache); if (md->active < md->pages && !is_vm_hugetlb_page(vma)) seq_printf(m," active=%lu", md->active); if (md->writeback) seq_printf(m," writeback=%lu", md->writeback); for_each_online_node(n) if (md->node[n]) seq_printf(m, " N%d=%lu", n, md->node[n]); out: seq_putc(m, '\n'); kfree(md); if (m->count < m->size) m->version = (vma != priv->tail_vma) ? vma->vm_start : 0; return 0; }