1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (C) 2017-2018 Bartosz Golaszewski <brgl@bgdev.pl> 4 * Copyright (C) 2020 Bartosz Golaszewski <bgolaszewski@baylibre.com> 5 */ 6 7 #include <linux/cleanup.h> 8 #include <linux/interrupt.h> 9 #include <linux/irq.h> 10 #include <linux/irq_sim.h> 11 #include <linux/irq_work.h> 12 #include <linux/slab.h> 13 14 struct irq_sim_work_ctx { 15 struct irq_work work; 16 int irq_base; 17 unsigned int irq_count; 18 unsigned long *pending; 19 struct irq_domain *domain; 20 struct irq_sim_ops ops; 21 void *user_data; 22 }; 23 24 struct irq_sim_irq_ctx { 25 bool enabled; 26 struct irq_sim_work_ctx *work_ctx; 27 }; 28 29 static void irq_sim_irqmask(struct irq_data *data) 30 { 31 struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data); 32 33 irq_ctx->enabled = false; 34 } 35 36 static void irq_sim_irqunmask(struct irq_data *data) 37 { 38 struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data); 39 40 irq_ctx->enabled = true; 41 } 42 43 static int irq_sim_set_type(struct irq_data *data, unsigned int type) 44 { 45 /* We only support rising and falling edge trigger types. */ 46 if (type & ~IRQ_TYPE_EDGE_BOTH) 47 return -EINVAL; 48 49 irqd_set_trigger_type(data, type); 50 51 return 0; 52 } 53 54 static int irq_sim_get_irqchip_state(struct irq_data *data, 55 enum irqchip_irq_state which, bool *state) 56 { 57 struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data); 58 irq_hw_number_t hwirq = irqd_to_hwirq(data); 59 60 switch (which) { 61 case IRQCHIP_STATE_PENDING: 62 if (irq_ctx->enabled) 63 *state = test_bit(hwirq, irq_ctx->work_ctx->pending); 64 break; 65 default: 66 return -EINVAL; 67 } 68 69 return 0; 70 } 71 72 static int irq_sim_set_irqchip_state(struct irq_data *data, 73 enum irqchip_irq_state which, bool state) 74 { 75 struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data); 76 irq_hw_number_t hwirq = irqd_to_hwirq(data); 77 78 switch (which) { 79 case IRQCHIP_STATE_PENDING: 80 if (irq_ctx->enabled) { 81 assign_bit(hwirq, irq_ctx->work_ctx->pending, state); 82 if (state) 83 irq_work_queue(&irq_ctx->work_ctx->work); 84 } 85 break; 86 default: 87 return -EINVAL; 88 } 89 90 return 0; 91 } 92 93 static int irq_sim_request_resources(struct irq_data *data) 94 { 95 struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data); 96 struct irq_sim_work_ctx *work_ctx = irq_ctx->work_ctx; 97 irq_hw_number_t hwirq = irqd_to_hwirq(data); 98 99 if (work_ctx->ops.irq_sim_irq_requested) 100 return work_ctx->ops.irq_sim_irq_requested(work_ctx->domain, 101 hwirq, 102 work_ctx->user_data); 103 104 return 0; 105 } 106 107 static void irq_sim_release_resources(struct irq_data *data) 108 { 109 struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data); 110 struct irq_sim_work_ctx *work_ctx = irq_ctx->work_ctx; 111 irq_hw_number_t hwirq = irqd_to_hwirq(data); 112 113 if (work_ctx->ops.irq_sim_irq_released) 114 work_ctx->ops.irq_sim_irq_released(work_ctx->domain, hwirq, 115 work_ctx->user_data); 116 } 117 118 static struct irq_chip irq_sim_irqchip = { 119 .name = "irq_sim", 120 .irq_mask = irq_sim_irqmask, 121 .irq_unmask = irq_sim_irqunmask, 122 .irq_set_type = irq_sim_set_type, 123 .irq_get_irqchip_state = irq_sim_get_irqchip_state, 124 .irq_set_irqchip_state = irq_sim_set_irqchip_state, 125 .irq_request_resources = irq_sim_request_resources, 126 .irq_release_resources = irq_sim_release_resources, 127 }; 128 129 static void irq_sim_handle_irq(struct irq_work *work) 130 { 131 struct irq_sim_work_ctx *work_ctx; 132 unsigned int offset = 0; 133 int irqnum; 134 135 work_ctx = container_of(work, struct irq_sim_work_ctx, work); 136 137 while (!bitmap_empty(work_ctx->pending, work_ctx->irq_count)) { 138 offset = find_next_bit(work_ctx->pending, 139 work_ctx->irq_count, offset); 140 clear_bit(offset, work_ctx->pending); 141 irqnum = irq_find_mapping(work_ctx->domain, offset); 142 handle_simple_irq(irq_to_desc(irqnum)); 143 } 144 } 145 146 static int irq_sim_domain_map(struct irq_domain *domain, 147 unsigned int virq, irq_hw_number_t hw) 148 { 149 struct irq_sim_work_ctx *work_ctx = domain->host_data; 150 struct irq_sim_irq_ctx *irq_ctx; 151 152 irq_ctx = kzalloc(sizeof(*irq_ctx), GFP_KERNEL); 153 if (!irq_ctx) 154 return -ENOMEM; 155 156 irq_set_chip(virq, &irq_sim_irqchip); 157 irq_set_chip_data(virq, irq_ctx); 158 irq_set_handler(virq, handle_simple_irq); 159 irq_modify_status(virq, IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE); 160 irq_ctx->work_ctx = work_ctx; 161 162 return 0; 163 } 164 165 static void irq_sim_domain_unmap(struct irq_domain *domain, unsigned int virq) 166 { 167 struct irq_sim_irq_ctx *irq_ctx; 168 struct irq_data *irqd; 169 170 irqd = irq_domain_get_irq_data(domain, virq); 171 irq_ctx = irq_data_get_irq_chip_data(irqd); 172 173 irq_set_handler(virq, NULL); 174 irq_domain_reset_irq_data(irqd); 175 kfree(irq_ctx); 176 } 177 178 static const struct irq_domain_ops irq_sim_domain_ops = { 179 .map = irq_sim_domain_map, 180 .unmap = irq_sim_domain_unmap, 181 }; 182 183 /** 184 * irq_domain_create_sim - Create a new interrupt simulator irq_domain and 185 * allocate a range of dummy interrupts. 186 * 187 * @fwnode: struct fwnode_handle to be associated with this domain. 188 * @num_irqs: Number of interrupts to allocate. 189 * 190 * On success: return a new irq_domain object. 191 * On failure: a negative errno wrapped with ERR_PTR(). 192 */ 193 struct irq_domain *irq_domain_create_sim(struct fwnode_handle *fwnode, 194 unsigned int num_irqs) 195 { 196 return irq_domain_create_sim_full(fwnode, num_irqs, NULL, NULL); 197 } 198 EXPORT_SYMBOL_GPL(irq_domain_create_sim); 199 200 struct irq_domain *irq_domain_create_sim_full(struct fwnode_handle *fwnode, 201 unsigned int num_irqs, 202 const struct irq_sim_ops *ops, 203 void *data) 204 { 205 struct irq_sim_work_ctx *work_ctx __free(kfree) = 206 kmalloc(sizeof(*work_ctx), GFP_KERNEL); 207 208 if (!work_ctx) 209 return ERR_PTR(-ENOMEM); 210 211 unsigned long *pending __free(bitmap) = bitmap_zalloc(num_irqs, GFP_KERNEL); 212 if (!pending) 213 return ERR_PTR(-ENOMEM); 214 215 work_ctx->domain = irq_domain_create_linear(fwnode, num_irqs, 216 &irq_sim_domain_ops, 217 work_ctx); 218 if (!work_ctx->domain) 219 return ERR_PTR(-ENOMEM); 220 221 work_ctx->irq_count = num_irqs; 222 work_ctx->work = IRQ_WORK_INIT_HARD(irq_sim_handle_irq); 223 work_ctx->pending = no_free_ptr(pending); 224 work_ctx->user_data = data; 225 226 if (ops) 227 memcpy(&work_ctx->ops, ops, sizeof(*ops)); 228 229 return no_free_ptr(work_ctx)->domain; 230 } 231 EXPORT_SYMBOL_GPL(irq_domain_create_sim_full); 232 233 /** 234 * irq_domain_remove_sim - Deinitialize the interrupt simulator domain: free 235 * the interrupt descriptors and allocated memory. 236 * 237 * @domain: The interrupt simulator domain to tear down. 238 */ 239 void irq_domain_remove_sim(struct irq_domain *domain) 240 { 241 struct irq_sim_work_ctx *work_ctx = domain->host_data; 242 243 irq_work_sync(&work_ctx->work); 244 bitmap_free(work_ctx->pending); 245 kfree(work_ctx); 246 247 irq_domain_remove(domain); 248 } 249 EXPORT_SYMBOL_GPL(irq_domain_remove_sim); 250 251 static void devm_irq_domain_remove_sim(void *data) 252 { 253 struct irq_domain *domain = data; 254 255 irq_domain_remove_sim(domain); 256 } 257 258 /** 259 * devm_irq_domain_create_sim - Create a new interrupt simulator for 260 * a managed device. 261 * 262 * @dev: Device to initialize the simulator object for. 263 * @fwnode: struct fwnode_handle to be associated with this domain. 264 * @num_irqs: Number of interrupts to allocate 265 * 266 * On success: return a new irq_domain object. 267 * On failure: a negative errno wrapped with ERR_PTR(). 268 */ 269 struct irq_domain *devm_irq_domain_create_sim(struct device *dev, 270 struct fwnode_handle *fwnode, 271 unsigned int num_irqs) 272 { 273 return devm_irq_domain_create_sim_full(dev, fwnode, num_irqs, 274 NULL, NULL); 275 } 276 EXPORT_SYMBOL_GPL(devm_irq_domain_create_sim); 277 278 struct irq_domain * 279 devm_irq_domain_create_sim_full(struct device *dev, 280 struct fwnode_handle *fwnode, 281 unsigned int num_irqs, 282 const struct irq_sim_ops *ops, 283 void *data) 284 { 285 struct irq_domain *domain; 286 int ret; 287 288 domain = irq_domain_create_sim_full(fwnode, num_irqs, ops, data); 289 if (IS_ERR(domain)) 290 return domain; 291 292 ret = devm_add_action_or_reset(dev, devm_irq_domain_remove_sim, domain); 293 if (ret) 294 return ERR_PTR(ret); 295 296 return domain; 297 } 298 EXPORT_SYMBOL_GPL(devm_irq_domain_create_sim_full); 299
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.