summaryrefslogtreecommitdiffstats
path: root/kernel/irq/irq_sim.c
blob: b992f88c561337a2b312b9b451de1732b493ea62 (plain)
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
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2017-2018 Bartosz Golaszewski <brgl@bgdev.pl>
 */

#include <linux/slab.h>
#include <linux/irq_sim.h>
#include <linux/irq.h>

struct irq_sim_devres {
	struct irq_sim		*sim;
};

static void irq_sim_irqmask(struct irq_data *data)
{
	struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data);

	irq_ctx->enabled = false;
}

static void irq_sim_irqunmask(struct irq_data *data)
{
	struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data);

	irq_ctx->enabled = true;
}

static int irq_sim_set_type(struct irq_data *data, unsigned int type)
{
	/* We only support rising and falling edge trigger types. */
	if (type & ~IRQ_TYPE_EDGE_BOTH)
		return -EINVAL;

	irqd_set_trigger_type(data, type);

	return 0;
}

static struct irq_chip irq_sim_irqchip = {
	.name		= "irq_sim",
	.irq_mask	= irq_sim_irqmask,
	.irq_unmask	= irq_sim_irqunmask,
	.irq_set_type	= irq_sim_set_type,
};

static void irq_sim_handle_irq(struct irq_work *work)
{
	struct irq_sim_work_ctx *work_ctx;
	unsigned int offset = 0;
	struct irq_sim *sim;
	int irqnum;

	work_ctx = container_of(work, struct irq_sim_work_ctx, work);
	sim = container_of(work_ctx, struct irq_sim, work_ctx);

	while (!bitmap_empty(work_ctx->pending, sim->irq_count)) {
		offset = find_next_bit(work_ctx->pending,
				       sim->irq_count, offset);
		clear_bit(offset, work_ctx->pending);
		irqnum = irq_sim_irqnum(sim, offset);
		handle_simple_irq(irq_to_desc(irqnum));
	}
}

/**
 * irq_sim_init - Initialize the interrupt simulator: allocate a range of
 *                dummy interrupts.
 *
 * @sim:        The interrupt simulator object to initialize.
 * @num_irqs:   Number of interrupts to allocate
 *
 * On success: return the base of the allocated interrupt range.
 * On failure: a negative errno.
 */
int irq_sim_init(struct irq_sim *sim, unsigned int num_irqs)
{
	int i;

	sim->irqs = kmalloc_array(num_irqs, sizeof(*sim->irqs), GFP_KERNEL);
	if (!sim->irqs)
		return -ENOMEM;

	sim->irq_base = irq_alloc_descs(-1, 0, num_irqs, 0);
	if (sim->irq_base < 0) {
		kfree(sim->irqs);
		return sim->irq_base;
	}

	sim->work_ctx.pending = bitmap_zalloc(num_irqs, GFP_KERNEL);
	if (!sim->work_ctx.pending) {
		kfree(sim->irqs);
		irq_free_descs(sim->irq_base, num_irqs);
		return -ENOMEM;
	}

	for (i = 0; i < num_irqs; i++) {
		sim->irqs[i].irqnum = sim->irq_base + i;
		sim->irqs[i].enabled = false;
		irq_set_chip(sim->irq_base + i, &irq_sim_irqchip);
		irq_set_chip_data(sim->irq_base + i, &sim->irqs[i]);
		irq_set_handler(sim->irq_base + i, &handle_simple_irq);
		irq_modify_status(sim->irq_base + i,
				  IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE);
	}

	init_irq_work(&sim->work_ctx.work, irq_sim_handle_irq);
	sim->irq_count = num_irqs;

	return sim->irq_base;
}
EXPORT_SYMBOL_GPL(irq_sim_init);

/**
 * irq_sim_fini - Deinitialize the interrupt simulator: free the interrupt
 *                descriptors and allocated memory.
 *
 * @sim:        The interrupt simulator to tear down.
 */
void irq_sim_fini(struct irq_sim *sim)
{
	irq_work_sync(&sim->work_ctx.work);
	bitmap_free(sim->work_ctx.pending);
	irq_free_descs(sim->irq_base, sim->irq_count);
	kfree(sim->irqs);
}
EXPORT_SYMBOL_GPL(irq_sim_fini);

static void devm_irq_sim_release(struct device *dev, void *res)
{
	struct irq_sim_devres *this = res;

	irq_sim_fini(this->sim);
}

/**
 * irq_sim_init - Initialize the interrupt simulator for a managed device.
 *
 * @dev:        Device to initialize the simulator object for.
 * @sim:        The interrupt simulator object to initialize.
 * @num_irqs:   Number of interrupts to allocate
 *
 * On success: return the base of the allocated interrupt range.
 * On failure: a negative errno.
 */
int devm_irq_sim_init(struct device *dev, struct irq_sim *sim,
		      unsigned int num_irqs)
{
	struct irq_sim_devres *dr;
	int rv;

	dr = devres_alloc(devm_irq_sim_release, sizeof(*dr), GFP_KERNEL);
	if (!dr)
		return -ENOMEM;

	rv = irq_sim_init(sim, num_irqs);
	if (rv < 0) {
		devres_free(dr);
		return rv;
	}

	dr->sim = sim;
	devres_add(dev, dr);

	return rv;
}
EXPORT_SYMBOL_GPL(devm_irq_sim_init);

/**
 * irq_sim_fire - Enqueue an interrupt.
 *
 * @sim:        The interrupt simulator object.
 * @offset:     Offset of the simulated interrupt which should be fired.
 */
void irq_sim_fire(struct irq_sim *sim, unsigned int offset)
{
	if (sim->irqs[offset].enabled) {
		set_bit(offset, sim->work_ctx.pending);
		irq_work_queue(&sim->work_ctx.work);
	}
}
EXPORT_SYMBOL_GPL(irq_sim_fire);

/**
 * irq_sim_irqnum - Get the allocated number of a dummy interrupt.
 *
 * @sim:        The interrupt simulator object.
 * @offset:     Offset of the simulated interrupt for which to retrieve
 *              the number.
 */
int irq_sim_irqnum(struct irq_sim *sim, unsigned int offset)
{
	return sim->irqs[offset].irqnum;
}
EXPORT_SYMBOL_GPL(irq_sim_irqnum);