/*------------------------------------------------------------------------------------------*\ * * Copyright (C) 2006 AVM GmbH * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA \*------------------------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------------------------*\ \*-----------------------------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include /*--- #include ---*/ #include #include #include #include #include #include /*--- #include ---*/ #define AVM_EVENT_INTERNAL #include #include #include #include "avm_sammel.h" #include "avm_event.h" /*------------------------------------------------------------------------------------------*\ * 2.4 Kernel H-Files \*------------------------------------------------------------------------------------------*/ #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE #include #endif /*--- #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE ---*/ static char *avm_event_function_names[64]; struct _dump_event_stat { unsigned short count; unsigned short max_datalength; unsigned short max_linkcount; }; /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static struct _avm_event_open_data *first, *last; static struct _avm_event_source *avm_event_source[MAX_AVM_EVENT_SOURCES]; struct _avm_event_node *avm_event_nodes[MAX_AVM_EVENT_NODES]; struct _avm_event_remote_source *avm_event_remote_source[MAX_AVM_EVENT_SOURCES]; static void sync_new_nodes(struct work_struct *work); DECLARE_WORK(sync_nodes_work, sync_new_nodes); /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void avm_event_source_notify(enum _avm_event_id id); /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ extern spinlock_t avm_event_lock; #define LOCAL_LOCK() spin_lock_irqsave(&avm_event_lock, flags) #define LOCAL_UNLOCK() spin_unlock_irqrestore(&avm_event_lock, flags) /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int atomic_test_and_set(unsigned int *ptr, unsigned int value) { int ret = 0; unsigned long flags; LOCAL_LOCK(); if(*ptr == 0) { *ptr = value; ret = 1; } LOCAL_UNLOCK(); return ret; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void atomic_set_bits_long_long(unsigned long long *Dest, unsigned long long or_mask) { unsigned long flags; LOCAL_LOCK(); *Dest |= or_mask; LOCAL_UNLOCK(); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void atomic_clear_bits_long_long(unsigned long long *Dest, unsigned long long or_mask) { unsigned long flags; LOCAL_LOCK(); *Dest &= ~or_mask; LOCAL_UNLOCK(); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void atomic_add_value(unsigned int *addr, signed int value) { unsigned long flags; LOCAL_LOCK(); *addr += value; LOCAL_UNLOCK(); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void atomic_add_to_front_of_queue(struct _avm_event_open_data *open_data) { unsigned long flags; open_data->next = open_data->prev = NULL; LOCAL_LOCK(); if(last == NULL) { /*--- aller erster Eintrag ---*/ last = open_data; } else { first->prev = open_data; open_data->next = first; } first = open_data; LOCAL_UNLOCK(); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void atomic_rm_from_queue(struct _avm_event_open_data *open_data) { unsigned long flags; LOCAL_LOCK(); if(open_data->prev) { open_data->prev->next = open_data->next; } else { /*--- (first == open_data) wenn es keinen Vorgaenger gibt, muss dieser Eintrag der erste sein ---*/ first = open_data->next; } if(open_data->next) { open_data->next->prev = open_data->prev; } else { /*--- (last == open_data) wenn es keine Nachfolger, muss dieser Eintrag der letzte sein ---*/ last = open_data->prev; } LOCAL_UNLOCK(); } #if !defined(CONFIG_NO_PRINTK) /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void dump_event_stat(char *prefix, struct _dump_event_stat *event_stat_tab) { unsigned int i; for(i = 0; i < avm_event_last; i++) { if(event_stat_tab->count) { if(prefix) { printk(KERN_ERR "%s", prefix); prefix = NULL; } printk(KERN_ERR "\t\tid: %s(%u): pending entries %u max-link-counts %u, max-len %u\n", avm_event_function_names[i] ? avm_event_function_names[i] : "?", i, event_stat_tab->count, event_stat_tab->max_linkcount, event_stat_tab->max_datalength); event_stat_tab->count = 0; event_stat_tab->max_linkcount = 0; event_stat_tab->max_datalength = 0; } event_stat_tab++; } } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void dump_pending_events(void) { unsigned long flags; struct _avm_event_open_data *open_data; struct _dump_event_stat evstat[avm_event_last]; char text[256]; struct _avm_event_item *I; unsigned int count = 0, i = 0, events = 0; open_data = first; /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ memset(&evstat, 0, sizeof(evstat)); while(open_data) { count++; LOCAL_LOCK(); I = (struct _avm_event_item *) open_data->item; if(I == NULL) { open_data = open_data->next; LOCAL_UNLOCK(); continue; } i = 0; snprintf(text, sizeof(text), "user %u \"%s\" pending events:\n", count, open_data->Name ? open_data->Name : "unknown"); while(I) { struct _avm_event_header *event_header; i++; if(I->data == NULL) { LOCAL_UNLOCK(); printk(KERN_ERR "%s\t\titem %u: ERROR no DATA\n", text, i); LOCAL_LOCK(); break; } if(I->data->data == NULL) { LOCAL_UNLOCK(); printk(KERN_ERR "%s\t\titem: %u ERROR no DATAPOINTER: len %u\n", text, i, I->data->data_length); LOCAL_LOCK(); break; } event_header = (struct _avm_event_header *)(I->data->data); if(event_header->id >= avm_event_last) { LOCAL_UNLOCK(); printk(KERN_ERR "%s\t\titem %u ERROR invalid id: %u\n", text, i, event_header->id); LOCAL_LOCK(); break; } events++; evstat[event_header->id].count++; if(evstat[event_header->id].max_datalength < I->data->data_length) { evstat[event_header->id].max_datalength = I->data->data_length; } if(evstat[event_header->id].max_linkcount < I->data->link_count) { evstat[event_header->id].max_linkcount = I->data->link_count; } I = (struct _avm_event_item *) I->next; } LOCAL_UNLOCK(); dump_event_stat(text, evstat); open_data = open_data->next; } printk(KERN_ERR "Summary: %u user and %u pending events\n", count, events); } #else #define dump_pending_events() #endif /*--- #if !defined(CONFIG_NO_PRINTK) ---*/ /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ int avm_event_register(struct _avm_event_open_data *open_data, struct _avm_event_cmd_param_register *data) { int i; DEB_INFO("[avm_event_register]: Name=%s Mask=%LX\n", data->Name, data->mask); atomic_add_to_front_of_queue(open_data); DEB_INFO("[avm_event_register]: open_data=0x%p first=0x%p last=0x%p\n", open_data, first, last); strcpy(open_data->Name, data->Name); open_data->event_mask_registered = data->mask; for(i = 0 ; i < avm_event_last ; i++) { if(open_data->event_mask_registered & ((unsigned long long)1 << i)) { avm_event_source_notify((enum _avm_event_id)i); } } DEB_INFO("[avm_event_register]: success\n"); return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ int avm_event_release(struct _avm_event_open_data *open_data, struct _avm_event_cmd_param_release *data __attribute__((unused))) { unsigned int i; DEB_INFO("[avm_event_release]: first=0x%p last=0x%p\n", first, last); atomic_rm_from_queue(open_data); for(i = 0 ; i < sizeof(unsigned long long) * 8 ; i++) { if(open_data->event_mask_registered & ((unsigned long long)1 << i)) { while(open_data->item) { avm_event_commit(open_data, i); } } } DEB_INFO("[avm_event_release]: (first=%x last=%x) success\n", (unsigned int)first, (unsigned int)last); return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ int avm_event_trigger(struct _avm_event_open_data *open_data, struct _avm_event_cmd_param_trigger *data) { DEB_INFO("[avm_event_trigger]: Id %u\n", data->id); if(open_data->event_mask_registered & ((unsigned long long)1 << data->id)) { avm_event_source_notify((enum _avm_event_id)data->id); } else { DEB_WARN("[avm_event_trigger]: Id %u event mask not registered\n", data->id); } return 0; } /*------------------------------------------------------------------------------------------*\ * Den ersten Eintrag für den man benachrichtigt wurde holen \*------------------------------------------------------------------------------------------*/ int avm_event_get(struct _avm_event_open_data *open_data, unsigned char **rx_buffer, unsigned int *rx_buffer_length, unsigned int *event_pos __attribute__((unused))) { if(open_data->item) { *rx_buffer = open_data->item->data->data; *rx_buffer_length = open_data->item->data->data_length; DEB_INFO("[avm_event_get]: success\n"); return 0; } DEB_INFO("[avm_event_get]: empty\n"); return 0; } /*------------------------------------------------------------------------------------------*\ * Ruecksetzen der notified Maske, herunterzaehlen des link_count und ggf. freigaben * der event_data \*------------------------------------------------------------------------------------------*/ void avm_event_commit(struct _avm_event_open_data *open_data, unsigned int event_pos __attribute__((unused))) { unsigned long flags; struct _avm_event_item *I = (struct _avm_event_item *) open_data->item; DEB_INFO("[avm_event_commit]: Name=%s\n", open_data->Name); if(I) { /*--- wenn ein event item vorhanden ---*/ /*----------------------------------------------------------------------------------*\ * geschuetztes manipulieren der Liste (erstes Element endfernen) \*----------------------------------------------------------------------------------*/ #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE save_flags(flags); cli(); open_data->item = I->next; /*--- das naechste Element ---*/ restore_flags(flags); #else /*--- #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE ---*/ spin_lock_irqsave(&avm_event_lock, flags); open_data->item = I->next; /*--- das naechste Element ---*/ spin_unlock_irqrestore(&avm_event_lock, flags); #endif /*--- #else ---*/ /*--- #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE ---*/ /*----------------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------------*/ /*--- avm_event_free_data(I->data); ---*/ avm_event_free_item(I); } /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ { int count = 0; unsigned char iii[64]; memset(iii, 0, sizeof(iii)); I = (struct _avm_event_item *)open_data->item; while(I) { if(I->data) { struct _avm_event_header *event_header; event_header = (struct _avm_event_header *)(I->data->data); iii[event_header->id]++; } I = (struct _avm_event_item *)I->next; count++; } DEB_INFO("[avm_event_commit]: Name=%s \n", open_data->Name); for(count = 0 ; count < 64 ; count++) { if(iii[count]) { /*--- BUG FIX, Events ---*/ #if 0 if(iii[count] > 20) { struct _avm_event_item *II = (struct _avm_event_item *) open_data->item; int i; for(i = 0 ; i < 15 ; i++) { spin_lock_irqsave(&avm_event_lock, flags); open_data->item = II->next; /*--- das naechste Element ---*/ spin_unlock_irqrestore(&avm_event_lock, flags); avm_event_free_item(II); } printk(KERN_ERR "WARNING: id: %d: %d events pending, 15 event dropped\n", count, iii[count]); iii[count] -= 15; } else { #endif DEB_INFO("\tid: %d: %d events\n", count, iii[count]); #if 0 } #endif } } } open_data->receive_count++; return; } /*------------------------------------------------------------------------------------------*\ * wird NUR im Kontext der sich registrierenden Applikation aufgerufen, somit ist die * 'avm_event_sema' gesetzt \*------------------------------------------------------------------------------------------*/ static void avm_event_source_notify(enum _avm_event_id id) { int i; for( i = 0 ; i < MAX_AVM_EVENT_SOURCES ; i++) { if(avm_event_source[i] == NULL) continue; if(avm_event_source[i]->notify && (avm_event_source[i]->event_mask & ((unsigned long long)1 << id))) { (*avm_event_source[i]->notify)(avm_event_source[i]->Context, id); break; /*--- eine id kann nur von einem geliefert werden ---*/ } } } /*--------------------------------------------------------------------------------*\ * p_id_mask: dummy * Baut die Event-Mask zusammen * num_ids: Anzahl der folgenden num_ids * * -> Code kann auch ohne Event2.0 avm_event_build_id_mask() verwenden \*--------------------------------------------------------------------------------*/ unsigned long long avm_event_build_id_mask(struct _avm_event_id_mask *p_id_mask __maybe_unused, int num_ids, ... ){ unsigned long long id_mask = 0; int i; va_list listPointer; va_start(listPointer, num_ids); if(num_ids == 0) { return id_mask; } for(i = 0; i < num_ids; i++ ) { enum _avm_event_id id = va_arg( listPointer, enum _avm_event_id); if((unsigned int)id >= avm_event_last) { /*--- id out of range ---*/ return 0; } id_mask |= (unsigned long long)1 << (id); } va_end(listPointer); return id_mask; } EXPORT_SYMBOL(avm_event_build_id_mask); /*------------------------------------------------------------------------------------------*\ * wird von beiden Kontexten aufgerufen \*------------------------------------------------------------------------------------------*/ void *avm_event_source_register(char *name, unsigned long long id_mask, void (*notify)(void *, enum _avm_event_id), void *Context) { unsigned int i; struct _avm_event_source *S; DEB_INFO("[avm_event_source_register]: name=%s mask=%LX\n", name, id_mask); if(id_mask & avm_event_source_mask) { DEB_ERR("[avm_event_source_register]: overlapping event_mask current=%LX new=%LX\n", id_mask, avm_event_source_mask); return NULL; } if(strlen(name) > MAX_EVENT_SOURCE_NAME_LEN) { DEB_ERR("[%s]: Event name '%s' is too long!\n", __FUNCTION__, name); return NULL; } S = vmalloc(sizeof(struct _avm_event_source)); if(S == NULL) { DEB_ERR("[avm_event_source_register]: out of memory \n"); return NULL; } if(avm_event_remote_source_register(NULL, name, id_mask) != 0){ DEB_ERR("[%s] registering source %s with parent failed.\n", __func__, name); vfree(S); return NULL; } strncpy(S->Name, name, MAX_EVENT_SOURCE_NAME_LEN); S->Name[MAX_EVENT_SOURCE_NAME_LEN] = 0; S->signatur = AVM_EVENT_SIGNATUR; S->notify = notify; S->Context = Context; S->event_mask = id_mask; S->send_count = 0; for( i = 0 ; i < MAX_AVM_EVENT_SOURCES ; i++) { if(atomic_test_and_set((unsigned int *)&avm_event_source[i], (unsigned int)S)) { break; } } if(i == MAX_AVM_EVENT_SOURCES) { vfree(S); avm_event_remote_source_release(NULL, id_mask); DEB_ERR("[avm_event_source_register]: out of resources\n"); return NULL; } atomic_set_bits_long_long(&avm_event_source_mask, id_mask); DEB_INFO("[avm_event_source_register]: handle=0x%X success\n", (unsigned int)S); return (void *)S; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void avm_event_remote_source_notify(void *ctx, enum _avm_event_id event_id) { struct _avm_event_remote_source *source; struct _avm_event_node *node; if(ctx == NULL){ DEB_ERR("[%s] ctx == NULL!\n", __func__); return; } source = (struct _avm_event_remote_source *) ctx; node = avm_event_nodes[source->node_id]; if(node && node->source_trigger){ node->source_trigger(node, event_id); } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ int avm_event_remote_source_register(struct _avm_event_node *node, char *name, uint64_t id_mask) { unsigned int i, src_idx, rem_src_idx; struct _avm_event_remote_source *S; struct _avm_event_node *parent = NULL, *child = NULL; int result; DEB_TRACE("[%s]: name=%s mask=%LX\n", __func__, name, id_mask); /* * is there already a local source for the events? */ if(id_mask & avm_event_source_mask) { DEB_ERR("[%s]: overlapping local event_mask current=%LX new=%LX\n", __func__, id_mask, avm_event_source_mask); return -EEXIST; } /* * are there remote sources for this event? */ for(i = 0; i < MAX_AVM_EVENT_SOURCES; ++i){ if(avm_event_remote_source[i] && (avm_event_remote_source[i]->source.event_mask & id_mask)){ DEB_ERR("[%s] id_mask overlapping with remote event source %s\n", __func__, avm_event_remote_source[i]->source.Name); return -EEXIST; } } /* * is the name too long? */ if(strlen(name) > MAX_EVENT_SOURCE_NAME_LEN) { DEB_ERR("[%s]: Event name '%s' is too long!\n", __FUNCTION__, name); return -EINVAL; } /* * here is where things get real. Try to register the event source with our parent */ parent = avm_event_nodes[0]; if(parent && (parent != node)){ result = parent->source_register(parent, name, id_mask); if(result){ DEB_ERR("[%s] registering remote source %s with parent failed.\n", __func__, name); return result; } } if(node){ /* * the source is registered by a remote node. Create a local proxy source. */ S = vmalloc(sizeof(struct _avm_event_remote_source)); if(S == NULL) { DEB_ERR("[%s]: out of memory \n", __func__); return -ENOMEM; } strncpy(S->source.Name, name, MAX_EVENT_SOURCE_NAME_LEN); S->source.Name[MAX_EVENT_SOURCE_NAME_LEN] = '\0'; S->source.signatur = AVM_EVENT_SIGNATUR; S->source.event_mask = id_mask; S->source.notify = avm_event_remote_source_notify; S->source.Context = S; S->source.send_count = 0; S->node_id = node->id; for(src_idx = 0 ; src_idx < MAX_AVM_EVENT_SOURCES ; ++src_idx){ if(atomic_test_and_set((unsigned int *)&avm_event_source[src_idx], (unsigned int)&(S->source))) { S->local_idx = src_idx; break; } } if(src_idx < MAX_AVM_EVENT_SOURCES){ for(rem_src_idx = 0 ; rem_src_idx < MAX_AVM_EVENT_SOURCES ; ++rem_src_idx){ if(atomic_test_and_set((unsigned int *)&avm_event_remote_source[rem_src_idx], (unsigned int)S)) { break; } } if(rem_src_idx >= MAX_AVM_EVENT_SOURCES) { avm_event_source[S->local_idx] = NULL; vfree(S); DEB_ERR("[%s]: out of resources\n", __func__); if(parent){ parent->source_unregister(parent, id_mask); } return -ENOMEM; } } atomic_set_bits_long_long(&avm_event_source_mask, id_mask); DEB_INFO("[%s]: handle=0x%X success\n", __func__, (unsigned int)S); } for(i = 1; i < MAX_AVM_EVENT_NODES; ++i){ child = avm_event_nodes[i]; if(child && (child != node)) { if(child->source_register(child, name, id_mask) != 0){ DEB_ERR("[%s] registering remote event source %s with child %s failed.", __func__, name, child->name); } } } #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE MOD_INC_USE_COUNT; #endif /*--- #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE ---*/ return 0; } /*------------------------------------------------------------------------------------------*\ * wird von beiden Kontexten aufgerufen \*------------------------------------------------------------------------------------------*/ void avm_event_source_release(void *handle) { struct _avm_event_source *S = (struct _avm_event_source *)handle; unsigned int i; DEB_INFO("[avm_event_source_release]: handle=0x%X\n", (unsigned int)handle); if(handle == NULL) { DEB_ERR("[avm_event_source_release]: invalid handle NULL\n"); return; } if(S->signatur != AVM_EVENT_SIGNATUR) { DEB_ERR("[avm_event_source_release]: missing signatur\n"); return; } avm_event_remote_source_release(NULL, S->event_mask); atomic_clear_bits_long_long(&avm_event_source_mask, S->event_mask); for( i = 0 ; i < MAX_AVM_EVENT_SOURCES ; i++) { if(S == avm_event_source[i]) { avm_event_source[i] = NULL; vfree(S); break; } } DEB_INFO("[avm_event_source_release]: success\n"); return; } /*------------------------------------------------------------------------------------------*\ * wird von beiden Kontexten aufgerufen \*------------------------------------------------------------------------------------------*/ int avm_event_remote_source_release(struct _avm_event_node *node, uint64_t id_mask) { struct _avm_event_node *other = NULL; struct _avm_event_remote_source *source = NULL; int i; if(node){ /* * try to find the remote source for the id_mask */ for(i = 0; i < MAX_AVM_EVENT_SOURCES; ++i){ if(avm_event_remote_source[i] && avm_event_remote_source[i]->source.event_mask == id_mask){ source = avm_event_remote_source[i]; avm_event_remote_source[i] = NULL; break; } } if(source){ /* * we found a remote source entry. Remove the local proxy source and remember * the node index this source was registered from. */ atomic_clear_bits_long_long(&avm_event_source_mask, id_mask); if(avm_event_source[source->local_idx] == &(source->source)){ avm_event_source[source->local_idx] = NULL; } else { DEB_ERR("[%s] local proxy source mismatch!\n", __func__); } vfree(source); } } /* * notify all nodes that this event source is gone. If we are relaying, skip the node * this source was registered from. */ for(i = 0; i < MAX_AVM_EVENT_SOURCES; ++i){ other = avm_event_nodes[i]; if(other == NULL || other == node) continue; if(other && other->source_unregister(other, id_mask) != 0){ DEB_WARN("[%s] unregistering remote event source failed for node %s", __func__, other->name); } } DEB_INFO("[%s]: success\n", __func__); #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE MOD_DEC_USE_COUNT; #endif /*--- #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE ---*/ return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ int avm_event_source_check_id(void *handle, enum _avm_event_id id) { struct _avm_event_open_data *O = first; DEB_INFO("[avm_event_source_check_id]: handle=0x%x id=%u\n", (unsigned int)handle, id); if(handle == NULL) { DEB_ERR("[avm_event_source_trigger]: not registered\n"); return 0; } while(O) { /*--- alle geoeffneten handles durchgehen ---*/ /*--- jeder der auf diese id ein bit in seiner Maske gesetzt hat, wird mit informationen versorgt und anschliessend geweckt ---*/ if(O->event_mask_registered & ((unsigned long long)1 << id)) { return 1; } O = O->next; } return 0; } /*------------------------------------------------------------------------------------------*\ * wird von beiden Kontexten aufgerufen \*------------------------------------------------------------------------------------------*/ static int avm_event_source_trigger_local(void *handle, enum _avm_event_id id, unsigned int data_length, void *data) { struct _avm_event_data *D; struct _avm_event_open_data *O = first; struct _avm_event_header *H; struct _avm_event_source *S; int status; DEB_TRACE("[%s]: handle=0x%x id=%u data=0x%p len=%u (%s)\n", __func__, (unsigned int)handle, id, data, data_length, current->comm); H = (struct _avm_event_header *)data; if(handle == NULL) { DEB_ERR("[%s]: not registered\n", __func__); if(data) kfree(data); return 0; } S = (struct _avm_event_source *)handle; S->send_count++; if(!H || H->id != id) { DEB_ERR("[%s]: avm_event_header inkorrekt !\n", __func__); if(data) kfree(data); return 0; } D = avm_event_alloc_data(); if(D == NULL) { DEB_ERR("[%s]: out of memory (data descriptors) context=%s\n", __func__, current->comm); dump_pending_events(); if(data) kfree(data); return 0; } D->link_count = 1; D->data = data; D->data_length = data_length; status = 0; while(O) { /*--- alle geoeffneten handles durchgehen ---*/ /*--- jeder der auf diese id ein bit in seiner Maske gesetzt hat, wird mit informationen versorgt und anschliessend geweckt ---*/ if(O->event_mask_registered & ((unsigned long long)1 << id)) { if(avm_event_source_trigger_one(O, D)) { /* kein speicher mehr */ break; } status++; } O = O->next; } avm_event_free_data(D); DEB_INFO("[%s]: success (%u instances notified)\n", __func__, D ? D->link_count : 0); return status; /*--- anzahl der benachrichtigten empfaenger ---*/ } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ int avm_event_source_trigger(void *handle, enum _avm_event_id id, unsigned int data_length, void *data) { int result; result = avm_event_remote_source_trigger(NULL, id, data_length, data); if(result){ DEB_ERR("[%s] propagating event to remote nodes failed!\n", __func__); } return avm_event_source_trigger_local(handle, id, data_length, data); } /*------------------------------------------------------------------------------------------*\ * wird von beiden Kontexten aufgerufen \*------------------------------------------------------------------------------------------*/ int avm_event_source_trigger_one(struct _avm_event_open_data *O, struct _avm_event_data *D) { unsigned long flags; struct _avm_event_item *I; I = avm_event_alloc_item(); if(I == NULL) { DEB_ERR("[avm_event_source_trigger_one]: out of memory (items) context=%s\n", current->comm); dump_pending_events(); return 1; } I->data = D; I->next = NULL; atomic_add_value(&(D->link_count), 1); /*--- D->link_count++; ---*/ /*--------------------------------------------------------------------------------------*\ * geschueztes verketten (einhaengen) \*--------------------------------------------------------------------------------------*/ LOCAL_LOCK(); if(O->item == NULL) { /*--- erster ---*/ O->item = I; } else { /*--- ende finden und anhängen ---*/ struct _avm_event_item *i = (struct _avm_event_item *) O->item; while(i->next) /*--- bis zum Ende die schlange abarbeiten ---*/ i = (struct _avm_event_item *) i->next; i->next = I; } LOCAL_UNLOCK(); /*--- dump_pending_events(NULL, 0); ---*/ /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ DEB_INFO("[avm_event_source_trigger_one]: wake up '%s'\n", O->Name); O->queue_count++; wake_up(&(O->wait_queue)); return 0; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ int avm_event_remote_source_trigger(struct _avm_event_node *node, enum _avm_event_id id, unsigned int data_length, void *data) { struct _avm_event_remote_source *source; struct _avm_event_node *other; unsigned int i; int result, status; status = 0; if(node){ /* * called from a remote node. Propagate event to local listeners */ for(i = 0; i < MAX_AVM_EVENT_SOURCES; ++i){ /* * find local proxy source and trigger event for it */ source = avm_event_remote_source[i]; if(source == NULL || source->node_id != node->id) continue; if(source->source.event_mask & ((unsigned long long)1 << id)){ result = avm_event_source_trigger_local(&(source->source), id, data_length, data); if(result != 0){ status = result; DEB_ERR("[%s] error triggering local proxy source %s, return code %d", __func__, source->source.Name, result); } break; } } } /* * propagate event to other nodes */ for(i = 0; i < MAX_AVM_EVENT_NODES; ++i){ other = avm_event_nodes[i]; if(other == NULL || other == node) continue; if(other->sink_trigger){ result = other->sink_trigger(other, id, data_length, data); if(result != 0){ status = result; DEB_ERR("[%s] error triggering remote node %s, return code %d", __func__, other->name, result); } } } return status; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ int avm_event_node_register(struct _avm_event_node *node, unsigned int is_parent) { unsigned int id; unsigned long flags; int result = 0; BUG_ON( !node || !node->source_register || !node->source_unregister || !node->source_trigger || !node->sink_register || !node->sink_unregister || !node->name); DEB_TRACE("[%s] node: %s is_parent: %d\n", __func__, node->name, is_parent); LOCAL_LOCK(); if(is_parent){ if(avm_event_nodes[0] == NULL){ id = 0; node->id = id; node->synced = 1; avm_event_nodes[id] = node; } else { DEB_ERR("[%s] Event node %s tried to register as parent, but node %s already registered.\n", __func__, node->name, avm_event_nodes[0]->name); id = MAX_AVM_EVENT_NODES; } } else { for(id = 1; id < MAX_AVM_EVENT_NODES; ++id){ DEB_TRACE("[%s] avm_event_nodes[%d]: %p\n", __func__, id, avm_event_nodes[id]); if(avm_event_nodes[id] == NULL){ node->synced = 0; node->id = id; avm_event_nodes[id] = node; schedule_work(&sync_nodes_work); break; } } } LOCAL_UNLOCK(); if(id >= MAX_AVM_EVENT_NODES){ DEB_ERR("[%s] Unable to register node %s\n", __func__, node->name); result = -ENFILE; } return result; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ int avm_event_node_release(struct _avm_event_node *node) { int i, result = 0; if(avm_event_nodes[node->id] != node){ DEB_ERR("[%s] node data mismatch for node %s!\n", __func__, node->name); return -EINVAL; } for(i = 0; i < MAX_AVM_EVENT_SOURCES; ++i){ if(avm_event_remote_source[i] && avm_event_remote_source[i]->node_id == node->id){ result = avm_event_remote_source_release(node, avm_event_remote_source[i]->source.event_mask); if(result != 0){ DEB_ERR("[%s] error releasing remote source", __func__); } } } avm_event_nodes[node->id] = NULL; vfree(node); return 0; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void sync_new_nodes(struct work_struct *work __attribute((unused))) { struct _avm_event_node *node; struct _avm_event_source *source; unsigned int i, j; for(i = 1; i < MAX_AVM_EVENT_NODES; ++i){ node = avm_event_nodes[i]; if(node == NULL || node->synced != 0){ continue; } for(j = 0; j < MAX_AVM_EVENT_SOURCES; ++j){ source = avm_event_source[j]; if(source == NULL){ continue; } if(node->source_register){ DEB_ERR("[%s] registering source %s on node %s,\n", __func__, source->Name, node->name); node->source_register(node, source->Name, source->event_mask); } } node->synced = 1; } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ EXPORT_SYMBOL(avm_event_source_check_id); EXPORT_SYMBOL(avm_event_source_trigger); EXPORT_SYMBOL(avm_event_source_release); EXPORT_SYMBOL(avm_event_source_register); EXPORT_SYMBOL(avm_event_node_register); EXPORT_SYMBOL(avm_event_node_release); EXPORT_SYMBOL(avm_event_remote_source_trigger); EXPORT_SYMBOL(avm_event_remote_source_register); EXPORT_SYMBOL(avm_event_remote_source_release); /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static char *avm_event_function_names[64] = { [0] = "avm_event_id_wlan_client_status", [7] = "avm_event_id_autoprov", [8] = "avm_event_id_usb_status", [11] = "avm_event_id_dsl_get_arch_kernel", [12] = "avm_event_id_dsl_set_arch", [13] = "avm_event_id_dsl_get_arch", [14] = "avm_event_id_dsl_set", [15] = "avm_event_id_dsl_get", [16] = "avm_event_id_dsl_status", [17] = "avm_event_id_dsl_connect_status", [19] = "avm_event_id_push_button", [20] = "avm_event_id_telefon_wlan_command", [21] = "avm_event_id_capiotcp_startstop", [22] = "avm_event_id_telefon_up", [23] = "avm_event_id_reboot_req", [24] = "avm_event_id_appl_status", [25] = "avm_event_id_led_status", [26] = "avm_event_id_led_info", [27] = "avm_event_id_telefonprofile", [28] = "avm_event_id_temperature", [29] = "avm_event_id_cpu_idle", [30] = "avm_event_id_powermanagment_status", [32] = "avm_event_id_ethernet_status", [33] = "avm_event_id_ethernet_connect_status", [40] = "avm_event_id_pm_ressourceinfo_status", [63] = "avm_event_id_user_source_notify", }; /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int avm_event_print_function_mask_names(struct seq_file *seq, uint64_t mask) { unsigned int i; int result = 0; for(i = 0 ; i < sizeof(uint64_t) * 8 ; i++) { if((mask & (1ULL << i)) == 0) continue; if(avm_event_function_names[i]) { result = seq_printf(seq, "%s ", avm_event_function_names[i]); } else { result = seq_printf(seq, "undef-%d ", i); } } return result; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int source_show(struct seq_file *seq, void *data __attribute__ ((unused)) ) { unsigned int count; struct _avm_event_source *S; unsigned long flags; seq_printf(seq, "[avm_event] list Event Source\n"); LOCAL_LOCK(); for(count = 0; count < ARRAY_SIZE(avm_event_source); count++) { S = avm_event_source[count]; if(S == NULL) continue; seq_printf(seq, "Source: %*.s sent:%5d notify: %60pF: Function: ", MAX_EVENT_SOURCE_NAME_LEN, S->Name, S->send_count, S->notify); avm_event_print_function_mask_names(seq, S->event_mask); seq_printf(seq, "\n"); } LOCAL_UNLOCK(); seq_printf(seq, "--------------------------\n"); return 0; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int source_open(struct inode *inode __attribute__((unused)), struct file *file) { return single_open(file, source_show, NULL); } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int sink_show(struct seq_file *seq, void *data __attribute__ ((unused)) ) { struct _avm_event_open_data *O; unsigned long flags; seq_printf(seq, "[avm_event] list Event Sink\n"); LOCAL_LOCK(); for(O = first ; O ; O = O->next) { seq_printf(seq, "Sink: %*.s: receive:%5d Functions: ", MAX_EVENT_SOURCE_NAME_LEN, O->Name, O->receive_count); avm_event_print_function_mask_names(seq, O->event_mask_registered); seq_printf(seq, "\n"); if(O->receive_count != O->queue_count) { seq_printf(seq, "\t\t%5d event still pending.\n", O->queue_count - O->receive_count); } } LOCAL_UNLOCK(); seq_printf(seq, "--------------------------\n"); return 0; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int sink_open(struct inode *inode __attribute__((unused)), struct file *file) { return single_open(file, sink_show, NULL); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void show_event_stat(struct seq_file *seq, struct _dump_event_stat *event_stat_tab) { unsigned int i; for(i = 0; i < avm_event_last; i++) { if(event_stat_tab->count) { seq_printf(seq, "\t\tid: %s(%u): pending entries %u max-link-counts %u, max-len %u\n", avm_event_function_names[i] ? avm_event_function_names[i] : "?", i, event_stat_tab->count, event_stat_tab->max_linkcount, event_stat_tab->max_datalength); event_stat_tab->count = 0; event_stat_tab->max_linkcount = 0; event_stat_tab->max_datalength = 0; } event_stat_tab++; } } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ int events_show(struct seq_file *seq, void *data __attribute__ ((unused))) { unsigned long flags; struct _avm_event_open_data *open_data; struct _dump_event_stat evstat[avm_event_last]; struct _avm_event_item *I; /*--- struct _avm_event_data *D; ---*/ unsigned int count = 0, i = 0, events = 0; LOCAL_LOCK(); memset(&evstat, 0, sizeof(evstat)); open_data = first; while(open_data) { count++; I = (struct _avm_event_item *) open_data->item; if(I == NULL) { open_data = open_data->next; continue; } i = 0; seq_printf(seq, "user %u \"%s\" pending events:\n", count, open_data->Name ? open_data->Name : "unknown"); while(I) { struct _avm_event_header *event_header; i++; if(I->data == NULL) { seq_printf(seq, "\t\titem %u: ERROR no DATA\n", i); break; } if(I->data->data == NULL) { seq_printf(seq, "\t\titem: %u ERROR no DATAPOINTER: len %u\n", i, I->data->data_length); break; } event_header = (struct _avm_event_header *)(I->data->data); if(event_header->id >= avm_event_last) { seq_printf(seq, "\t\titem %u ERROR invalid id: %u\n", i, event_header->id); break; } events++; evstat[event_header->id].count++; if(evstat[event_header->id].max_datalength < I->data->data_length) { evstat[event_header->id].max_datalength = I->data->data_length; } if(evstat[event_header->id].max_linkcount < I->data->link_count) { evstat[event_header->id].max_linkcount = I->data->link_count; } I = (struct _avm_event_item *) I->next; } show_event_stat(seq, evstat); open_data = open_data->next; } LOCAL_UNLOCK(); seq_printf(seq, "Summary: %u user and %u pending events\n", count, events); return 0; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int events_open(struct inode *inode __attribute__((unused)), struct file *file) { return single_open(file, events_show, NULL); } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static const struct file_operations sink_fops = { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32) .owner = THIS_MODULE, #endif .open = sink_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations source_fops = { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32) .owner = THIS_MODULE, #endif .open = source_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations events_fops = { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32) .owner = THIS_MODULE, #endif .open = events_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static struct proc_dir_entry* event_dir; void avm_event_proc_init(void) { event_dir = proc_mkdir("avm/event", NULL); proc_create("sink", 0, event_dir, &sink_fops); proc_create("source", 0, event_dir, &source_fops); proc_create("events", 0, event_dir, &events_fops); } #if defined(AVM_EVENT_MODULE) /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ void avm_event_proc_exit(void) { remove_proc_entry("sink", event_dir); remove_proc_entry("source", event_dir); remove_proc_entry("events", event_dir); remove_proc_entry("avm_event", NULL); } #endif/*--- #if defined(AVM_EVENT_MODULE) ---*/