diff options
Diffstat (limited to 'drivers/ide/ide-proc.c')
| -rw-r--r-- | drivers/ide/ide-proc.c | 344 | 
1 files changed, 325 insertions, 19 deletions
diff --git a/drivers/ide/ide-proc.c b/drivers/ide/ide-proc.c index a9e0b30fb1f..d50bd996ff2 100644 --- a/drivers/ide/ide-proc.c +++ b/drivers/ide/ide-proc.c @@ -3,6 +3,8 @@   *   *  Copyright (C) 1997-1998	Mark Lord   *  Copyright (C) 2003		Red Hat <alan@redhat.com> + * + *  Some code was moved here from ide.c, see it for original copyrights.   */  /* @@ -37,6 +39,8 @@  #include <asm/io.h> +static struct proc_dir_entry *proc_ide_root; +  static int proc_ide_read_imodel  	(char *page, char **start, off_t off, int count, int *eof, void *data)  { @@ -121,6 +125,265 @@ static int proc_ide_read_identify  	PROC_IDE_READ_RETURN(page,start,off,count,eof,len);  } +/** + *	__ide_add_setting	-	add an ide setting option + *	@drive: drive to use + *	@name: setting name + *	@rw: true if the function is read write + *	@data_type: type of data + *	@min: range minimum + *	@max: range maximum + *	@mul_factor: multiplication scale + *	@div_factor: divison scale + *	@data: private data field + *	@set: setting + *	@auto_remove: setting auto removal flag + * + *	Removes the setting named from the device if it is present. + *	The function takes the settings_lock to protect against + *	parallel changes. This function must not be called from IRQ + *	context. Returns 0 on success or -1 on failure. + * + *	BUGS: This code is seriously over-engineered. There is also + *	magic about how the driver specific features are setup. If + *	a driver is attached we assume the driver settings are auto + *	remove. + */ + +static int __ide_add_setting(ide_drive_t *drive, const char *name, int rw, int data_type, int min, int max, int mul_factor, int div_factor, void *data, ide_procset_t *set, int auto_remove) +{ +	ide_settings_t **p = (ide_settings_t **) &drive->settings, *setting = NULL; + +	down(&ide_setting_sem); +	while ((*p) && strcmp((*p)->name, name) < 0) +		p = &((*p)->next); +	if ((setting = kzalloc(sizeof(*setting), GFP_KERNEL)) == NULL) +		goto abort; +	if ((setting->name = kmalloc(strlen(name) + 1, GFP_KERNEL)) == NULL) +		goto abort; +	strcpy(setting->name, name); +	setting->rw = rw; +	setting->data_type = data_type; +	setting->min = min; +	setting->max = max; +	setting->mul_factor = mul_factor; +	setting->div_factor = div_factor; +	setting->data = data; +	setting->set = set; + +	setting->next = *p; +	if (auto_remove) +		setting->auto_remove = 1; +	*p = setting; +	up(&ide_setting_sem); +	return 0; +abort: +	up(&ide_setting_sem); +	kfree(setting); +	return -1; +} + +int ide_add_setting(ide_drive_t *drive, const char *name, int rw, int data_type, int min, int max, int mul_factor, int div_factor, void *data, ide_procset_t *set) +{ +	return __ide_add_setting(drive, name, rw, data_type, min, max, mul_factor, div_factor, data, set, 1); +} + +EXPORT_SYMBOL(ide_add_setting); + +/** + *	__ide_remove_setting	-	remove an ide setting option + *	@drive: drive to use + *	@name: setting name + * + *	Removes the setting named from the device if it is present. + *	The caller must hold the setting semaphore. + */ + +static void __ide_remove_setting (ide_drive_t *drive, char *name) +{ +	ide_settings_t **p, *setting; + +	p = (ide_settings_t **) &drive->settings; + +	while ((*p) && strcmp((*p)->name, name)) +		p = &((*p)->next); +	if ((setting = (*p)) == NULL) +		return; + +	(*p) = setting->next; + +	kfree(setting->name); +	kfree(setting); +} + +/** + *	auto_remove_settings	-	remove driver specific settings + *	@drive: drive + * + *	Automatically remove all the driver specific settings for this + *	drive. This function may not be called from IRQ context. The + *	caller must hold ide_setting_sem. + */ + +static void auto_remove_settings (ide_drive_t *drive) +{ +	ide_settings_t *setting; +repeat: +	setting = drive->settings; +	while (setting) { +		if (setting->auto_remove) { +			__ide_remove_setting(drive, setting->name); +			goto repeat; +		} +		setting = setting->next; +	} +} + +/** + *	ide_find_setting_by_name	-	find a drive specific setting + *	@drive: drive to scan + *	@name: setting name + * + *	Scan's the device setting table for a matching entry and returns + *	this or NULL if no entry is found. The caller must hold the + *	setting semaphore + */ + +static ide_settings_t *ide_find_setting_by_name(ide_drive_t *drive, char *name) +{ +	ide_settings_t *setting = drive->settings; + +	while (setting) { +		if (strcmp(setting->name, name) == 0) +			break; +		setting = setting->next; +	} +	return setting; +} + +/** + *	ide_read_setting	-	read an IDE setting + *	@drive: drive to read from + *	@setting: drive setting + * + *	Read a drive setting and return the value. The caller + *	must hold the ide_setting_sem when making this call. + * + *	BUGS: the data return and error are the same return value + *	so an error -EINVAL and true return of the same value cannot + *	be told apart + */ + +static int ide_read_setting(ide_drive_t *drive, ide_settings_t *setting) +{ +	int		val = -EINVAL; +	unsigned long	flags; + +	if ((setting->rw & SETTING_READ)) { +		spin_lock_irqsave(&ide_lock, flags); +		switch(setting->data_type) { +			case TYPE_BYTE: +				val = *((u8 *) setting->data); +				break; +			case TYPE_SHORT: +				val = *((u16 *) setting->data); +				break; +			case TYPE_INT: +				val = *((u32 *) setting->data); +				break; +		} +		spin_unlock_irqrestore(&ide_lock, flags); +	} +	return val; +} + +/** + *	ide_write_setting	-	read an IDE setting + *	@drive: drive to read from + *	@setting: drive setting + *	@val: value + * + *	Write a drive setting if it is possible. The caller + *	must hold the ide_setting_sem when making this call. + * + *	BUGS: the data return and error are the same return value + *	so an error -EINVAL and true return of the same value cannot + *	be told apart + * + *	FIXME:  This should be changed to enqueue a special request + *	to the driver to change settings, and then wait on a sema for completion. + *	The current scheme of polling is kludgy, though safe enough. + */ + +static int ide_write_setting(ide_drive_t *drive, ide_settings_t *setting, int val) +{ +	if (!capable(CAP_SYS_ADMIN)) +		return -EACCES; +	if (setting->set) +		return setting->set(drive, val); +	if (!(setting->rw & SETTING_WRITE)) +		return -EPERM; +	if (val < setting->min || val > setting->max) +		return -EINVAL; +	if (ide_spin_wait_hwgroup(drive)) +		return -EBUSY; +	switch (setting->data_type) { +		case TYPE_BYTE: +			*((u8 *) setting->data) = val; +			break; +		case TYPE_SHORT: +			*((u16 *) setting->data) = val; +			break; +		case TYPE_INT: +			*((u32 *) setting->data) = val; +			break; +	} +	spin_unlock_irq(&ide_lock); +	return 0; +} + +static int set_xfer_rate (ide_drive_t *drive, int arg) +{ +	int err; + +	if (arg < 0 || arg > 70) +		return -EINVAL; + +	err = ide_wait_cmd(drive, +			WIN_SETFEATURES, (u8) arg, +			SETFEATURES_XFER, 0, NULL); + +	if (!err && arg) { +		ide_set_xfer_rate(drive, (u8) arg); +		ide_driveid_update(drive); +	} +	return err; +} + +/** + *	ide_add_generic_settings	-	generic ide settings + *	@drive: drive being configured + * + *	Add the generic parts of the system settings to the /proc files. + *	The caller must not be holding the ide_setting_sem. + */ + +void ide_add_generic_settings (ide_drive_t *drive) +{ +/* + *			  drive		setting name		read/write access				data type	min	max				mul_factor	div_factor	data pointer			set function + */ +	__ide_add_setting(drive,	"io_32bit",		drive->no_io_32bit ? SETTING_READ : SETTING_RW,	TYPE_BYTE,	0,	1 + (SUPPORT_VLB_SYNC << 1),	1,		1,		&drive->io_32bit,		set_io_32bit,	0); +	__ide_add_setting(drive,	"keepsettings",		SETTING_RW,					TYPE_BYTE,	0,	1,				1,		1,		&drive->keep_settings,		NULL,		0); +	__ide_add_setting(drive,	"nice1",		SETTING_RW,					TYPE_BYTE,	0,	1,				1,		1,		&drive->nice1,			NULL,		0); +	__ide_add_setting(drive,	"pio_mode",		SETTING_WRITE,					TYPE_BYTE,	0,	255,				1,		1,		NULL,				set_pio_mode,	0); +	__ide_add_setting(drive,	"unmaskirq",		drive->no_unmask ? SETTING_READ : SETTING_RW,	TYPE_BYTE,	0,	1,				1,		1,		&drive->unmask,			NULL,		0); +	__ide_add_setting(drive,	"using_dma",		SETTING_RW,					TYPE_BYTE,	0,	1,				1,		1,		&drive->using_dma,		set_using_dma,	0); +	__ide_add_setting(drive,	"init_speed",		SETTING_RW,					TYPE_BYTE,	0,	70,				1,		1,		&drive->init_speed,		NULL,		0); +	__ide_add_setting(drive,	"current_speed",	SETTING_RW,					TYPE_BYTE,	0,	70,				1,		1,		&drive->current_speed,		set_xfer_rate,	0); +	__ide_add_setting(drive,	"number",		SETTING_RW,					TYPE_BYTE,	0,	3,				1,		1,		&drive->dn,			NULL,		0); +} +  static void proc_ide_settings_warn(void)  {  	static int warned = 0; @@ -399,7 +662,7 @@ static ide_proc_entry_t generic_drive_entries[] = {  	{ NULL,	0, NULL, NULL }  }; -void ide_add_proc_entries(struct proc_dir_entry *dir, ide_proc_entry_t *p, void *data) +static void ide_add_proc_entries(struct proc_dir_entry *dir, ide_proc_entry_t *p, void *data)  {  	struct proc_dir_entry *ent; @@ -415,7 +678,7 @@ void ide_add_proc_entries(struct proc_dir_entry *dir, ide_proc_entry_t *p, void  	}  } -void ide_remove_proc_entries(struct proc_dir_entry *dir, ide_proc_entry_t *p) +static void ide_remove_proc_entries(struct proc_dir_entry *dir, ide_proc_entry_t *p)  {  	if (!dir || !p)  		return; @@ -425,6 +688,51 @@ void ide_remove_proc_entries(struct proc_dir_entry *dir, ide_proc_entry_t *p)  	}  } +void ide_proc_register_driver(ide_drive_t *drive, ide_driver_t *driver) +{ +	ide_add_proc_entries(drive->proc, driver->proc, drive); +} + +EXPORT_SYMBOL(ide_proc_register_driver); + +/** + *	ide_proc_unregister_driver	-	remove driver specific data + *	@drive: drive + *	@driver: driver + * + *	Clean up the driver specific /proc files and IDE settings + *	for a given drive. + * + *	Takes ide_setting_sem and ide_lock. + *	Caller must hold none of the locks. + */ + +void ide_proc_unregister_driver(ide_drive_t *drive, ide_driver_t *driver) +{ +	unsigned long flags; + +	ide_remove_proc_entries(drive->proc, driver->proc); + +	down(&ide_setting_sem); +	spin_lock_irqsave(&ide_lock, flags); +	/* +	 * ide_setting_sem protects the settings list +	 * ide_lock protects the use of settings +	 * +	 * so we need to hold both, ide_settings_sem because we want to +	 * modify the settings list, and ide_lock because we cannot take +	 * a setting out that is being used. +	 * +	 * OTOH both ide_{read,write}_setting are only ever used under +	 * ide_setting_sem. +	 */ +	auto_remove_settings(drive); +	spin_unlock_irqrestore(&ide_lock, flags); +	up(&ide_setting_sem); +} + +EXPORT_SYMBOL(ide_proc_unregister_driver); +  static void create_proc_ide_drives(ide_hwif_t *hwif)  {  	int	d; @@ -477,26 +785,24 @@ static ide_proc_entry_t hwif_entries[] = {  	{ NULL,	0, NULL, NULL }  }; -void create_proc_ide_interfaces(void) +void ide_proc_register_port(ide_hwif_t *hwif)  { -	int	h; +	if (!hwif->present) +		return; -	for (h = 0; h < MAX_HWIFS; h++) { -		ide_hwif_t *hwif = &ide_hwifs[h]; +	if (!hwif->proc) { +		hwif->proc = proc_mkdir(hwif->name, proc_ide_root); -		if (!hwif->present) -			continue; -		if (!hwif->proc) { -			hwif->proc = proc_mkdir(hwif->name, proc_ide_root); -			if (!hwif->proc) -				return; -			ide_add_proc_entries(hwif->proc, hwif_entries, hwif); -		} -		create_proc_ide_drives(hwif); +		if (!hwif->proc) +			return; + +		ide_add_proc_entries(hwif->proc, hwif_entries, hwif);  	} + +	create_proc_ide_drives(hwif);  } -EXPORT_SYMBOL(create_proc_ide_interfaces); +EXPORT_SYMBOL_GPL(ide_proc_register_port);  #ifdef CONFIG_BLK_DEV_IDEPCI  void ide_pci_create_host_proc(const char *name, get_info_t *get_info) @@ -507,7 +813,7 @@ void ide_pci_create_host_proc(const char *name, get_info_t *get_info)  EXPORT_SYMBOL_GPL(ide_pci_create_host_proc);  #endif -void destroy_proc_ide_interface(ide_hwif_t *hwif) +void ide_proc_unregister_port(ide_hwif_t *hwif)  {  	if (hwif->proc) {  		destroy_proc_ide_drives(hwif); @@ -554,11 +860,11 @@ void proc_ide_create(void)  {  	struct proc_dir_entry *entry; +	proc_ide_root = proc_mkdir("ide", NULL); +  	if (!proc_ide_root)  		return; -	create_proc_ide_interfaces(); -  	entry = create_proc_entry("drivers", 0, proc_ide_root);  	if (entry)  		entry->proc_fops = &ide_drivers_operations;  | 
