Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /*
0003  * Copyright (C) 2017-2018 Bartosz Golaszewski <brgl@bgdev.pl>
0004  * Copyright (C) 2020 Bartosz Golaszewski <bgolaszewski@baylibre.com>
0005  */
0006 
0007 #include <linux/irq.h>
0008 #include <linux/irq_sim.h>
0009 #include <linux/irq_work.h>
0010 #include <linux/interrupt.h>
0011 #include <linux/slab.h>
0012 
0013 struct irq_sim_work_ctx {
0014     struct irq_work     work;
0015     int         irq_base;
0016     unsigned int        irq_count;
0017     unsigned long       *pending;
0018     struct irq_domain   *domain;
0019 };
0020 
0021 struct irq_sim_irq_ctx {
0022     int         irqnum;
0023     bool            enabled;
0024     struct irq_sim_work_ctx *work_ctx;
0025 };
0026 
0027 static void irq_sim_irqmask(struct irq_data *data)
0028 {
0029     struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data);
0030 
0031     irq_ctx->enabled = false;
0032 }
0033 
0034 static void irq_sim_irqunmask(struct irq_data *data)
0035 {
0036     struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data);
0037 
0038     irq_ctx->enabled = true;
0039 }
0040 
0041 static int irq_sim_set_type(struct irq_data *data, unsigned int type)
0042 {
0043     /* We only support rising and falling edge trigger types. */
0044     if (type & ~IRQ_TYPE_EDGE_BOTH)
0045         return -EINVAL;
0046 
0047     irqd_set_trigger_type(data, type);
0048 
0049     return 0;
0050 }
0051 
0052 static int irq_sim_get_irqchip_state(struct irq_data *data,
0053                      enum irqchip_irq_state which, bool *state)
0054 {
0055     struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data);
0056     irq_hw_number_t hwirq = irqd_to_hwirq(data);
0057 
0058     switch (which) {
0059     case IRQCHIP_STATE_PENDING:
0060         if (irq_ctx->enabled)
0061             *state = test_bit(hwirq, irq_ctx->work_ctx->pending);
0062         break;
0063     default:
0064         return -EINVAL;
0065     }
0066 
0067     return 0;
0068 }
0069 
0070 static int irq_sim_set_irqchip_state(struct irq_data *data,
0071                      enum irqchip_irq_state which, bool state)
0072 {
0073     struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data);
0074     irq_hw_number_t hwirq = irqd_to_hwirq(data);
0075 
0076     switch (which) {
0077     case IRQCHIP_STATE_PENDING:
0078         if (irq_ctx->enabled) {
0079             assign_bit(hwirq, irq_ctx->work_ctx->pending, state);
0080             if (state)
0081                 irq_work_queue(&irq_ctx->work_ctx->work);
0082         }
0083         break;
0084     default:
0085         return -EINVAL;
0086     }
0087 
0088     return 0;
0089 }
0090 
0091 static struct irq_chip irq_sim_irqchip = {
0092     .name           = "irq_sim",
0093     .irq_mask       = irq_sim_irqmask,
0094     .irq_unmask     = irq_sim_irqunmask,
0095     .irq_set_type       = irq_sim_set_type,
0096     .irq_get_irqchip_state  = irq_sim_get_irqchip_state,
0097     .irq_set_irqchip_state  = irq_sim_set_irqchip_state,
0098 };
0099 
0100 static void irq_sim_handle_irq(struct irq_work *work)
0101 {
0102     struct irq_sim_work_ctx *work_ctx;
0103     unsigned int offset = 0;
0104     int irqnum;
0105 
0106     work_ctx = container_of(work, struct irq_sim_work_ctx, work);
0107 
0108     while (!bitmap_empty(work_ctx->pending, work_ctx->irq_count)) {
0109         offset = find_next_bit(work_ctx->pending,
0110                        work_ctx->irq_count, offset);
0111         clear_bit(offset, work_ctx->pending);
0112         irqnum = irq_find_mapping(work_ctx->domain, offset);
0113         handle_simple_irq(irq_to_desc(irqnum));
0114     }
0115 }
0116 
0117 static int irq_sim_domain_map(struct irq_domain *domain,
0118                   unsigned int virq, irq_hw_number_t hw)
0119 {
0120     struct irq_sim_work_ctx *work_ctx = domain->host_data;
0121     struct irq_sim_irq_ctx *irq_ctx;
0122 
0123     irq_ctx = kzalloc(sizeof(*irq_ctx), GFP_KERNEL);
0124     if (!irq_ctx)
0125         return -ENOMEM;
0126 
0127     irq_set_chip(virq, &irq_sim_irqchip);
0128     irq_set_chip_data(virq, irq_ctx);
0129     irq_set_handler(virq, handle_simple_irq);
0130     irq_modify_status(virq, IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE);
0131     irq_ctx->work_ctx = work_ctx;
0132 
0133     return 0;
0134 }
0135 
0136 static void irq_sim_domain_unmap(struct irq_domain *domain, unsigned int virq)
0137 {
0138     struct irq_sim_irq_ctx *irq_ctx;
0139     struct irq_data *irqd;
0140 
0141     irqd = irq_domain_get_irq_data(domain, virq);
0142     irq_ctx = irq_data_get_irq_chip_data(irqd);
0143 
0144     irq_set_handler(virq, NULL);
0145     irq_domain_reset_irq_data(irqd);
0146     kfree(irq_ctx);
0147 }
0148 
0149 static const struct irq_domain_ops irq_sim_domain_ops = {
0150     .map        = irq_sim_domain_map,
0151     .unmap      = irq_sim_domain_unmap,
0152 };
0153 
0154 /**
0155  * irq_domain_create_sim - Create a new interrupt simulator irq_domain and
0156  *                         allocate a range of dummy interrupts.
0157  *
0158  * @fwnode:     struct fwnode_handle to be associated with this domain.
0159  * @num_irqs:   Number of interrupts to allocate.
0160  *
0161  * On success: return a new irq_domain object.
0162  * On failure: a negative errno wrapped with ERR_PTR().
0163  */
0164 struct irq_domain *irq_domain_create_sim(struct fwnode_handle *fwnode,
0165                      unsigned int num_irqs)
0166 {
0167     struct irq_sim_work_ctx *work_ctx;
0168 
0169     work_ctx = kmalloc(sizeof(*work_ctx), GFP_KERNEL);
0170     if (!work_ctx)
0171         goto err_out;
0172 
0173     work_ctx->pending = bitmap_zalloc(num_irqs, GFP_KERNEL);
0174     if (!work_ctx->pending)
0175         goto err_free_work_ctx;
0176 
0177     work_ctx->domain = irq_domain_create_linear(fwnode, num_irqs,
0178                             &irq_sim_domain_ops,
0179                             work_ctx);
0180     if (!work_ctx->domain)
0181         goto err_free_bitmap;
0182 
0183     work_ctx->irq_count = num_irqs;
0184     work_ctx->work = IRQ_WORK_INIT_HARD(irq_sim_handle_irq);
0185 
0186     return work_ctx->domain;
0187 
0188 err_free_bitmap:
0189     bitmap_free(work_ctx->pending);
0190 err_free_work_ctx:
0191     kfree(work_ctx);
0192 err_out:
0193     return ERR_PTR(-ENOMEM);
0194 }
0195 EXPORT_SYMBOL_GPL(irq_domain_create_sim);
0196 
0197 /**
0198  * irq_domain_remove_sim - Deinitialize the interrupt simulator domain: free
0199  *                         the interrupt descriptors and allocated memory.
0200  *
0201  * @domain:     The interrupt simulator domain to tear down.
0202  */
0203 void irq_domain_remove_sim(struct irq_domain *domain)
0204 {
0205     struct irq_sim_work_ctx *work_ctx = domain->host_data;
0206 
0207     irq_work_sync(&work_ctx->work);
0208     bitmap_free(work_ctx->pending);
0209     kfree(work_ctx);
0210 
0211     irq_domain_remove(domain);
0212 }
0213 EXPORT_SYMBOL_GPL(irq_domain_remove_sim);
0214 
0215 static void devm_irq_domain_remove_sim(void *data)
0216 {
0217     struct irq_domain *domain = data;
0218 
0219     irq_domain_remove_sim(domain);
0220 }
0221 
0222 /**
0223  * devm_irq_domain_create_sim - Create a new interrupt simulator for
0224  *                              a managed device.
0225  *
0226  * @dev:        Device to initialize the simulator object for.
0227  * @fwnode:     struct fwnode_handle to be associated with this domain.
0228  * @num_irqs:   Number of interrupts to allocate
0229  *
0230  * On success: return a new irq_domain object.
0231  * On failure: a negative errno wrapped with ERR_PTR().
0232  */
0233 struct irq_domain *devm_irq_domain_create_sim(struct device *dev,
0234                           struct fwnode_handle *fwnode,
0235                           unsigned int num_irqs)
0236 {
0237     struct irq_domain *domain;
0238     int ret;
0239 
0240     domain = irq_domain_create_sim(fwnode, num_irqs);
0241     if (IS_ERR(domain))
0242         return domain;
0243 
0244     ret = devm_add_action_or_reset(dev, devm_irq_domain_remove_sim, domain);
0245     if (ret)
0246         return ERR_PTR(ret);
0247 
0248     return domain;
0249 }
0250 EXPORT_SYMBOL_GPL(devm_irq_domain_create_sim);