summaryrefslogtreecommitdiff
path: root/drivers/s390/scsi/zfcp_unit.c
blob: 1119c535a667318381409eee3626bc2dcb85f1fa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/*
 * zfcp device driver
 *
 * Tracking of manually configured LUNs and helper functions to
 * register the LUNs with the SCSI midlayer.
 *
 * Copyright IBM Corporation 2010
 */

#include "zfcp_def.h"
#include "zfcp_ext.h"

/**
 * zfcp_unit_scsi_scan - Register LUN with SCSI midlayer
 * @unit: The zfcp LUN/unit to register
 *
 * When the SCSI midlayer is not allowed to automatically scan and
 * attach SCSI devices, zfcp has to register the single devices with
 * the SCSI midlayer.
 */
void zfcp_unit_scsi_scan(struct zfcp_unit *unit)
{
	struct fc_rport *rport = unit->port->rport;
	unsigned int lun;

	lun = scsilun_to_int((struct scsi_lun *) &unit->fcp_lun);

	if (rport && rport->port_state == FC_PORTSTATE_ONLINE)
		scsi_scan_target(&rport->dev, 0, rport->scsi_target_id, lun, 1);
}

static void zfcp_unit_scsi_scan_work(struct work_struct *work)
{
	struct zfcp_unit *unit = container_of(work, struct zfcp_unit,
					      scsi_work);

	zfcp_unit_scsi_scan(unit);
	put_device(&unit->dev);
}

/**
 * zfcp_unit_queue_scsi_scan - Register configured units on port
 * @port: The zfcp_port where to register units
 *
 * After opening a port, all units configured on this port have to be
 * registered with the SCSI midlayer. This function should be called
 * after calling fc_remote_port_add, so that the fc_rport is already
 * ONLINE and the call to scsi_scan_target runs the same way as the
 * call in the FC transport class.
 */
void zfcp_unit_queue_scsi_scan(struct zfcp_port *port)
{
	struct zfcp_unit *unit;

	read_lock_irq(&port->unit_list_lock);
	list_for_each_entry(unit, &port->unit_list, list) {
		get_device(&unit->dev);
		if (scsi_queue_work(port->adapter->scsi_host,
				    &unit->scsi_work) <= 0)
			put_device(&unit->dev);
	}
	read_unlock_irq(&port->unit_list_lock);
}

static struct zfcp_unit *_zfcp_unit_find(struct zfcp_port *port, u64 fcp_lun)
{
	struct zfcp_unit *unit;

	list_for_each_entry(unit, &port->unit_list, list)
		if (unit->fcp_lun == fcp_lun) {
			get_device(&unit->dev);
			return unit;
		}

	return NULL;
}

/**
 * zfcp_unit_find - Find and return zfcp_unit with specified FCP LUN
 * @port: zfcp_port where to look for the unit
 * @fcp_lun: 64 Bit FCP LUN used to identify the zfcp_unit
 *
 * If zfcp_unit is found, a reference is acquired that has to be
 * released later.
 *
 * Returns: Pointer to the zfcp_unit, or NULL if there is no zfcp_unit
 *          with the specified FCP LUN.
 */
struct zfcp_unit *zfcp_unit_find(struct zfcp_port *port, u64 fcp_lun)
{
	struct zfcp_unit *unit;

	read_lock_irq(&port->unit_list_lock);
	unit = _zfcp_unit_find(port, fcp_lun);
	read_unlock_irq(&port->unit_list_lock);
	return unit;
}

/**
 * zfcp_unit_release - Drop reference to zfcp_port and free memory of zfcp_unit.
 * @dev: pointer to device in zfcp_unit
 */
static void zfcp_unit_release(struct device *dev)
{
	struct zfcp_unit *unit = container_of(dev, struct zfcp_unit, dev);

	put_device(&unit->port->dev);
	kfree(unit);
}

/**
 * zfcp_unit_enqueue - enqueue unit to unit list of a port.
 * @port: pointer to port where unit is added
 * @fcp_lun: FCP LUN of unit to be enqueued
 * Returns: 0 success
 *
 * Sets up some unit internal structures and creates sysfs entry.
 */
int zfcp_unit_add(struct zfcp_port *port, u64 fcp_lun)
{
	struct zfcp_unit *unit;

	unit = zfcp_unit_find(port, fcp_lun);
	if (unit) {
		put_device(&unit->dev);
		return -EEXIST;
	}

	unit = kzalloc(sizeof(struct zfcp_unit), GFP_KERNEL);
	if (!unit)
		return -ENOMEM;

	unit->port = port;
	unit->fcp_lun = fcp_lun;
	unit->dev.parent = &port->dev;
	unit->dev.release = zfcp_unit_release;
	INIT_WORK(&unit->scsi_work, zfcp_unit_scsi_scan_work);

	if (dev_set_name(&unit->dev, "0x%016llx",
			 (unsigned long long) fcp_lun)) {
		kfree(unit);
		return -ENOMEM;
	}

	if (device_register(&unit->dev)) {
		put_device(&unit->dev);
		return -ENOMEM;
	}

	if (sysfs_create_group(&unit->dev.kobj, &zfcp_sysfs_unit_attrs)) {
		device_unregister(&unit->dev);
		return -EINVAL;
	}

	get_device(&port->dev);

	write_lock_irq(&port->unit_list_lock);
	list_add_tail(&unit->list, &port->unit_list);
	write_unlock_irq(&port->unit_list_lock);

	zfcp_unit_scsi_scan(unit);

	return 0;
}

/**
 * zfcp_unit_sdev - Return SCSI device for zfcp_unit
 * @unit: The zfcp_unit where to get the SCSI device for
 *
 * Returns: scsi_device pointer on success, NULL if there is no SCSI
 *          device for this zfcp_unit
 *
 * On success, the caller also holds a reference to the SCSI device
 * that must be released with scsi_device_put.
 */
struct scsi_device *zfcp_unit_sdev(struct zfcp_unit *unit)
{
	struct Scsi_Host *shost;
	struct zfcp_port *port;
	unsigned int lun;

	lun = scsilun_to_int((struct scsi_lun *) &unit->fcp_lun);
	port = unit->port;
	shost = port->adapter->scsi_host;
	return scsi_device_lookup(shost, 0, port->starget_id, lun);
}

/**
 * zfcp_unit_sdev_status - Return zfcp LUN status for SCSI device
 * @unit: The unit to lookup the SCSI device for
 *
 * Returns the zfcp LUN status field of the SCSI device if the SCSI device
 * for the zfcp_unit exists, 0 otherwise.
 */
unsigned int zfcp_unit_sdev_status(struct zfcp_unit *unit)
{
	unsigned int status = 0;
	struct scsi_device *sdev;
	struct zfcp_scsi_dev *zfcp_sdev;

	sdev = zfcp_unit_sdev(unit);
	if (sdev) {
		zfcp_sdev = sdev_to_zfcp(sdev);
		status = atomic_read(&zfcp_sdev->status);
		scsi_device_put(sdev);
	}

	return status;
}

/**
 * zfcp_unit_remove - Remove entry from list of configured units
 * @port: The port where to remove the unit from the configuration
 * @fcp_lun: The 64 bit LUN of the unit to remove
 *
 * Returns: -EINVAL if a unit with the specified LUN does not exist,
 *          0 on success.
 */
int zfcp_unit_remove(struct zfcp_port *port, u64 fcp_lun)
{
	struct zfcp_unit *unit;
	struct scsi_device *sdev;

	write_lock_irq(&port->unit_list_lock);
	unit = _zfcp_unit_find(port, fcp_lun);
	if (unit)
		list_del(&unit->list);
	write_unlock_irq(&port->unit_list_lock);

	if (!unit)
		return -EINVAL;

	sdev = zfcp_unit_sdev(unit);
	if (sdev) {
		scsi_remove_device(sdev);
		scsi_device_put(sdev);
	}

	put_device(&unit->dev);

	zfcp_device_unregister(&unit->dev, &zfcp_sysfs_unit_attrs);

	return 0;
}