/**************************************************************************** * sched/irq/irq_procfs.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "irq/irq.h" #if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_PROCFS) #ifdef CONFIG_SCHED_IRQMONITOR /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Output format: * * 1111111111222222222233333333334444444444 * 1234567890123456789012345678901234567890123456789 * * IRQ HANDLER ARGUMENT COUNT RATE TIME * DDD XXXXXXXX XXXXXXXX DDDDDDDDDD DDDD.DDD DDDD * * NOTE: This assumes that an address can be represented in 32-bits. In * the typical configuration where CONFIG_HAVE_LONG_LONG=y, the COUNT field * may not be wide enough. */ #define HDR_FMT "IRQ HANDLER ARGUMENT COUNT RATE TIME\n" #define IRQ_FMT "%3u %08lx %08lx %10lu %4lu.%03lu %4lu\n" /* Determines the size of an intermediate buffer that must be large enough * to handle the longest line generated by this logic (plus a couple of * bytes). */ #define IRQ_LINELEN 50 /**************************************************************************** * Private Types ****************************************************************************/ /* This structure describes one open "file" */ struct irq_file_s { struct procfs_file_s base; /* Base open file structure */ FAR char *buffer; /* User provided buffer */ size_t remaining; /* Number of available characters in buffer */ size_t ncopied; /* Number of characters in buffer */ off_t offset; /* Current file offset */ char line[IRQ_LINELEN]; /* Pre-allocated buffer for formatted lines */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* irq_foreach() callback function */ static int irq_callback(int irq, FAR struct irq_info_s *info, FAR void *arg); /* File system methods */ static int irq_open(FAR struct file *filep, FAR const char *relpath, int oflags, mode_t mode); static int irq_close(FAR struct file *filep); static ssize_t irq_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static int irq_dup(FAR const struct file *oldp, FAR struct file *newp); static int irq_stat(FAR const char *relpath, FAR struct stat *buf); /**************************************************************************** * Public Data ****************************************************************************/ /* See fs_mount.c -- this structure is explicitly extern'ed there. * We use the old-fashioned kind of initializers so that this will compile * with any compiler. */ const struct procfs_operations g_irq_operations = { irq_open, /* open */ irq_close, /* close */ irq_read, /* read */ NULL, /* write */ NULL, /* poll */ irq_dup, /* dup */ NULL, /* opendir */ NULL, /* closedir */ NULL, /* readdir */ NULL, /* rewinddir */ irq_stat /* stat */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: irq_callback ****************************************************************************/ static int irq_callback(int irq, FAR struct irq_info_s *info, FAR void *arg) { FAR struct irq_file_s *irqfile = (FAR struct irq_file_s *)arg; struct irq_info_s copy; struct timespec delta; irqstate_t flags; clock_t elapsed; clock_t now; size_t linesize; size_t copysize; unsigned long intpart; unsigned long fracpart; unsigned long count; DEBUGASSERT(irqfile != NULL); /* Take a snapshot and reset the counts */ flags = enter_critical_section(); memcpy(©, info, sizeof(struct irq_info_s)); now = clock_systime_ticks(); info->start = now; info->time = 0; info->count = 0; leave_critical_section(flags); /* Don't bother if count == 0. * * REVISIT: There is a logic problem with skipping if the count is zero. * Normally this is a good thing because it makes the output concise. * However, it can be a problem under certain conditions: * * It may take multiple passes through the IRQ table to enumerate the * interrupts if the number of interrupts reported is large or if the size * of the user buffer is small. If a count is zero it will be skipped on * the first time through but if it becomes non-zero on the second time * through, the output will be corrupted. Similarly if the count is non- * zero the first time through and zero the second. * * A proper fix would require keep better track of where we left off * between passes. Current that position is remembered only by the * byte offset into the pseudo-file, f_pos. */ if (copy.count == 0) { return 0; } /* Calculate the interrupt rate from the interrupt count and the elapsed * time. * * REVISIT: If these counts have not been samples and reset in a long time * then the following will likely overflow. */ elapsed = now - copy.start; perf_convert(copy.time, &delta); #ifdef CONFIG_HAVE_LONG_LONG /* elapsed = - , units=clock ticks * rate = * TICKS_PER_SEC / elapsed */ elapsed = elapsed ? elapsed : 1; intpart = (unsigned int)((copy.count * TICK_PER_SEC) / elapsed); if (intpart >= 10000) { intpart = 9999; fracpart = 999; } else { uint64_t intcount = ((uint64_t)intpart * elapsed); fracpart = (unsigned int) (((copy.count * TICK_PER_SEC - intcount) * 1000) / elapsed); } /* Make sure that the count is representable with snprintf format */ if (copy.count > ULONG_MAX) { count = ULONG_MAX; } else { count = (unsigned long)copy.count; } #else # error Missing logic #endif /* Output information about this interrupt */ linesize = snprintf(irqfile->line, IRQ_LINELEN, IRQ_FMT, (unsigned int)irq, (unsigned long)((uintptr_t)copy.handler), (unsigned long)((uintptr_t)copy.arg), count, intpart, fracpart, (unsigned long)delta.tv_nsec / 1000); copysize = procfs_memcpy(irqfile->line, linesize, irqfile->buffer, irqfile->remaining, &irqfile->offset); irqfile->ncopied += copysize; irqfile->buffer += copysize; irqfile->remaining -= copysize; /* Return a non-zero value to stop the traversal if the user-provided * buffer is full. */ if (irqfile->remaining > 0) { return 0; } else { return 1; } } /**************************************************************************** * Name: irq_open ****************************************************************************/ static int irq_open(FAR struct file *filep, FAR const char *relpath, int oflags, mode_t mode) { FAR struct irq_file_s *irqfile; finfo("Open '%s'\n", relpath); /* This PROCFS file is read-only. Any attempt to open with write access * is not permitted. */ if ((oflags & O_WRONLY) != 0 || (oflags & O_RDONLY) == 0) { ferr("ERROR: Only O_RDONLY supported\n"); return -EACCES; } /* Allocate a container to hold the file attributes */ irqfile = kmm_zalloc(sizeof(struct irq_file_s)); if (!irqfile) { ferr("ERROR: Failed to allocate file attributes\n"); return -ENOMEM; } /* Save the attributes as the open-specific state in filep->f_priv */ filep->f_priv = (FAR void *)irqfile; return OK; } /**************************************************************************** * Name: irq_close ****************************************************************************/ static int irq_close(FAR struct file *filep) { FAR struct irq_file_s *irqfile; /* Recover our private data from the struct file instance */ irqfile = (FAR struct irq_file_s *)filep->f_priv; DEBUGASSERT(irqfile); /* Release the file attributes structure */ kmm_free(irqfile); filep->f_priv = NULL; return OK; } /**************************************************************************** * Name: irq_read ****************************************************************************/ static ssize_t irq_read(FAR struct file *filep, FAR char *buffer, size_t buflen) { FAR struct irq_file_s *irqfile; size_t linesize; size_t copysize; finfo("buffer=%p buflen=%d\n", buffer, (int)buflen); /* Recover our private data from the struct file instance */ irqfile = (FAR struct irq_file_s *)filep->f_priv; DEBUGASSERT(irqfile); /* Save the file offset and the user buffer information */ irqfile->offset = filep->f_pos; irqfile->buffer = buffer; irqfile->remaining = buflen; /* The first line to output is the header */ linesize = snprintf(irqfile->line, IRQ_LINELEN, HDR_FMT); copysize = procfs_memcpy(irqfile->line, linesize, irqfile->buffer, irqfile->remaining, &irqfile->offset); irqfile->ncopied = copysize; irqfile->buffer += copysize; irqfile->remaining -= copysize; /* Now traverse the list of attached interrupts, generating output for * each. */ irq_foreach(irq_callback, (FAR void *)irqfile); /* Update the file position */ filep->f_pos += irqfile->ncopied; return irqfile->ncopied; } /**************************************************************************** * Name: irq_dup * * Description: * Duplicate open file data in the new file structure. * ****************************************************************************/ static int irq_dup(FAR const struct file *oldp, FAR struct file *newp) { FAR struct irq_file_s *oldattr; FAR struct irq_file_s *newattr; finfo("Dup %p->%p\n", oldp, newp); /* Recover our private data from the old struct file instance */ oldattr = (FAR struct irq_file_s *)oldp->f_priv; DEBUGASSERT(oldattr); /* Allocate a new container to hold the task and attribute selection */ newattr = kmm_malloc(sizeof(struct irq_file_s)); if (!newattr) { ferr("ERROR: Failed to allocate file attributes\n"); return -ENOMEM; } /* The copy the file attributes from the old attributes to the new */ memcpy(newattr, oldattr, sizeof(struct irq_file_s)); /* Save the new attributes in the new file structure */ newp->f_priv = (FAR void *)newattr; return OK; } /**************************************************************************** * Name: irq_stat * * Description: Return information about a file or directory * ****************************************************************************/ static int irq_stat(const char *relpath, struct stat *buf) { /* "irqs" is the name for a read-only file */ memset(buf, 0, sizeof(struct stat)); buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR; return OK; } /**************************************************************************** * Public Functions ****************************************************************************/ #endif /* CONFIG_SCHED_IRQMONITOR */ #endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_PROCFS */