summaryrefslogtreecommitdiff
path: root/drivers/pci/hotplug/s390_pci_hpc.c
blob: c9e790c74051f6215b7c8f1057234f2a169ee965 (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
// SPDX-License-Identifier: GPL-2.0+
/*
 * PCI Hot Plug Controller Driver for System z
 *
 * Copyright 2012 IBM Corp.
 *
 * Author(s):
 *   Jan Glauber <jang@linux.vnet.ibm.com>
 */

#define KMSG_COMPONENT "zpci"
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <asm/pci_debug.h>
#include <asm/sclp.h>

#define SLOT_NAME_SIZE	10

static int zpci_fn_configured(enum zpci_state state)
{
	return state == ZPCI_FN_STATE_CONFIGURED ||
	       state == ZPCI_FN_STATE_ONLINE;
}

static inline int zdev_configure(struct zpci_dev *zdev)
{
	int ret = sclp_pci_configure(zdev->fid);

	zpci_dbg(3, "conf fid:%x, rc:%d\n", zdev->fid, ret);
	if (!ret)
		zdev->state = ZPCI_FN_STATE_CONFIGURED;

	return ret;
}

static inline int zdev_deconfigure(struct zpci_dev *zdev)
{
	int ret = sclp_pci_deconfigure(zdev->fid);

	zpci_dbg(3, "deconf fid:%x, rc:%d\n", zdev->fid, ret);
	if (!ret)
		zdev->state = ZPCI_FN_STATE_STANDBY;

	return ret;
}

static int enable_slot(struct hotplug_slot *hotplug_slot)
{
	struct zpci_dev *zdev = container_of(hotplug_slot, struct zpci_dev,
					     hotplug_slot);
	struct zpci_bus *zbus = zdev->zbus;
	int rc;

	if (zdev->state != ZPCI_FN_STATE_STANDBY)
		return -EIO;

	rc = zdev_configure(zdev);
	if (rc)
		return rc;

	rc = zpci_enable_device(zdev);
	if (rc)
		goto out_deconfigure;

	pci_scan_slot(zbus->bus, zdev->devfn);
	pci_lock_rescan_remove();
	pci_bus_add_devices(zbus->bus);
	pci_unlock_rescan_remove();

	return rc;

out_deconfigure:
	zdev_deconfigure(zdev);
	return rc;
}

static int disable_slot(struct hotplug_slot *hotplug_slot)
{
	struct zpci_dev *zdev = container_of(hotplug_slot, struct zpci_dev,
					     hotplug_slot);
	struct pci_dev *pdev;
	int rc;

	if (!zpci_fn_configured(zdev->state))
		return -EIO;

	pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn);
	if (pdev && pci_num_vf(pdev)) {
		pci_dev_put(pdev);
		return -EBUSY;
	}

	zpci_remove_device(zdev);

	rc = zpci_disable_device(zdev);
	if (rc)
		return rc;

	return zdev_deconfigure(zdev);
}

static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value)
{
	struct zpci_dev *zdev = container_of(hotplug_slot, struct zpci_dev,
					     hotplug_slot);

	switch (zdev->state) {
	case ZPCI_FN_STATE_STANDBY:
		*value = 0;
		break;
	default:
		*value = 1;
		break;
	}
	return 0;
}

static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
{
	/* if the slot exits it always contains a function */
	*value = 1;
	return 0;
}

static const struct hotplug_slot_ops s390_hotplug_slot_ops = {
	.enable_slot =		enable_slot,
	.disable_slot =		disable_slot,
	.get_power_status =	get_power_status,
	.get_adapter_status =	get_adapter_status,
};

int zpci_init_slot(struct zpci_dev *zdev)
{
	char name[SLOT_NAME_SIZE];
	struct zpci_bus *zbus = zdev->zbus;

	zdev->hotplug_slot.ops = &s390_hotplug_slot_ops;

	snprintf(name, SLOT_NAME_SIZE, "%08x", zdev->fid);
	return pci_hp_register(&zdev->hotplug_slot, zbus->bus,
			       zdev->devfn, name);
}

void zpci_exit_slot(struct zpci_dev *zdev)
{
	pci_hp_deregister(&zdev->hotplug_slot);
}