969 lines
28 KiB
C
969 lines
28 KiB
C
/******************************************************************************
|
|
*
|
|
* m a s t e r . c
|
|
*
|
|
* Methoden für einen EtherCAT-Master.
|
|
*
|
|
* $Id$
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/string.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include "../include/EtherCAT_rt.h"
|
|
#include "../include/EtherCAT_si.h"
|
|
#include "globals.h"
|
|
#include "master.h"
|
|
#include "slave.h"
|
|
#include "types.h"
|
|
#include "device.h"
|
|
#include "command.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Konstruktor des EtherCAT-Masters.
|
|
*/
|
|
|
|
void ec_master_init(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
master->slaves = NULL;
|
|
master->device = NULL;
|
|
|
|
INIT_LIST_HEAD(&master->commands);
|
|
INIT_LIST_HEAD(&master->domains);
|
|
|
|
ec_master_reset(master);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Destruktor eines EtherCAT-Masters.
|
|
|
|
Entfernt alle Kommandos aus der Liste, löscht den Zeiger
|
|
auf das Slave-Array und gibt die Prozessdaten frei.
|
|
*/
|
|
|
|
void ec_master_clear(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
ec_master_reset(master);
|
|
|
|
if (master->device) {
|
|
ec_device_clear(master->device);
|
|
kfree(master->device);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Setzt den Master zurück in den Ausgangszustand.
|
|
|
|
Bei einem "release" sollte immer diese Funktion aufgerufen werden,
|
|
da sonst Slave-Liste, Domains, etc. weiter existieren.
|
|
*/
|
|
|
|
void ec_master_reset(ec_master_t *master
|
|
/**< Zeiger auf den zurückzusetzenden Master */
|
|
)
|
|
{
|
|
unsigned int i;
|
|
ec_command_t *command, *next_command;
|
|
ec_domain_t *domain, *next_domain;
|
|
|
|
// Alle Slaves entfernen
|
|
if (master->slaves) {
|
|
for (i = 0; i < master->slave_count; i++) {
|
|
ec_slave_clear(master->slaves + i);
|
|
}
|
|
kfree(master->slaves);
|
|
master->slaves = NULL;
|
|
}
|
|
master->slave_count = 0;
|
|
|
|
// Kommando-Warteschlange leeren
|
|
list_for_each_entry_safe(command, next_command, &master->commands, list) {
|
|
command->state = EC_CMD_ERROR;
|
|
list_del_init(&command->list);
|
|
}
|
|
|
|
// Domain-Liste leeren
|
|
list_for_each_entry_safe(domain, next_domain, &master->domains, list) {
|
|
list_del(&domain->list);
|
|
ec_domain_clear(domain);
|
|
kfree(domain);
|
|
}
|
|
|
|
master->command_index = 0;
|
|
master->debug_level = 0;
|
|
master->stats.timeouts = 0;
|
|
master->stats.delayed = 0;
|
|
master->stats.corrupted = 0;
|
|
master->stats.unmatched = 0;
|
|
master->stats.t_last = 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Öffnet das EtherCAT-Geraet des Masters.
|
|
|
|
\return 0 wenn alles ok, < 0 wenn kein Gerät registriert wurde oder
|
|
es nicht geoeffnet werden konnte.
|
|
*/
|
|
|
|
int ec_master_open(ec_master_t *master /**< Der EtherCAT-Master */)
|
|
{
|
|
if (!master->device) {
|
|
EC_ERR("No device registered!\n");
|
|
return -1;
|
|
}
|
|
|
|
if (ec_device_open(master->device)) {
|
|
EC_ERR("Could not open device!\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Schliesst das EtherCAT-Geraet, auf dem der Master arbeitet.
|
|
*/
|
|
|
|
void ec_master_close(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
if (!master->device) {
|
|
EC_WARN("Warning - Trying to close an unregistered device!\n");
|
|
return;
|
|
}
|
|
|
|
if (ec_device_close(master->device))
|
|
EC_WARN("Warning - Could not close device!\n");
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Stellt ein Kommando in die Warteschlange.
|
|
*/
|
|
|
|
void ec_master_queue_command(ec_master_t *master, /**< EtherCAT-Master */
|
|
ec_command_t *command /**< Kommando */
|
|
)
|
|
{
|
|
ec_command_t *queued_command;
|
|
|
|
// Ist das Kommando schon in der Warteschlange?
|
|
list_for_each_entry(queued_command, &master->commands, list) {
|
|
if (queued_command == command) {
|
|
command->state = EC_CMD_QUEUED;
|
|
if (unlikely(master->debug_level))
|
|
EC_WARN("command already queued.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
list_add_tail(&command->list, &master->commands);
|
|
command->state = EC_CMD_QUEUED;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Sendet die Kommandos in der Warteschlange.
|
|
|
|
\return 0 bei Erfolg, sonst < 0
|
|
*/
|
|
|
|
void ec_master_send_commands(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
ec_command_t *command;
|
|
size_t command_size;
|
|
uint8_t *frame_data, *cur_data;
|
|
void *follows_word;
|
|
cycles_t start = 0, end;
|
|
|
|
if (unlikely(master->debug_level > 0)) {
|
|
EC_DBG("ec_master_send\n");
|
|
start = get_cycles();
|
|
}
|
|
|
|
// Zeiger auf Socket-Buffer holen
|
|
frame_data = ec_device_tx_data(master->device);
|
|
cur_data = frame_data + EC_FRAME_HEADER_SIZE;
|
|
follows_word = NULL;
|
|
|
|
// Aktuellen Frame mit Kommandos füllen
|
|
list_for_each_entry(command, &master->commands, list) {
|
|
if (command->state != EC_CMD_QUEUED) continue;
|
|
|
|
// Passt das aktuelle Kommando noch in den aktuellen Rahmen?
|
|
command_size = EC_COMMAND_HEADER_SIZE + command->data_size
|
|
+ EC_COMMAND_FOOTER_SIZE;
|
|
if (cur_data - frame_data + command_size > EC_MAX_FRAME_SIZE) break;
|
|
|
|
command->state = EC_CMD_SENT;
|
|
command->index = master->command_index++;
|
|
|
|
if (unlikely(master->debug_level > 0))
|
|
EC_DBG("adding command 0x%02X\n", command->index);
|
|
|
|
// Command-Following-Flag im letzten Kommando setzen
|
|
if (follows_word)
|
|
EC_WRITE_U16(follows_word, EC_READ_U16(follows_word) | 0x8000);
|
|
|
|
// EtherCAT command header
|
|
EC_WRITE_U8 (cur_data, command->type);
|
|
EC_WRITE_U8 (cur_data + 1, command->index);
|
|
EC_WRITE_U32(cur_data + 2, command->address.logical);
|
|
EC_WRITE_U16(cur_data + 6, command->data_size & 0x7FF);
|
|
EC_WRITE_U16(cur_data + 8, 0x0000);
|
|
follows_word = cur_data + 6;
|
|
cur_data += EC_COMMAND_HEADER_SIZE;
|
|
|
|
// EtherCAT command data
|
|
memcpy(cur_data, command->data, command->data_size);
|
|
cur_data += command->data_size;
|
|
|
|
// EtherCAT command footer
|
|
EC_WRITE_U16(cur_data, command->working_counter);
|
|
cur_data += EC_COMMAND_FOOTER_SIZE;
|
|
}
|
|
|
|
if (cur_data - frame_data == EC_FRAME_HEADER_SIZE) {
|
|
if (unlikely(master->debug_level > 0)) EC_DBG("nothing to send.\n");
|
|
return;
|
|
}
|
|
|
|
// EtherCAT frame header
|
|
EC_WRITE_U16(frame_data, ((cur_data - frame_data
|
|
- EC_FRAME_HEADER_SIZE) & 0x7FF) | 0x1000);
|
|
|
|
// Rahmen auffüllen
|
|
while (cur_data - frame_data < EC_MIN_FRAME_SIZE)
|
|
EC_WRITE_U8(cur_data++, 0x00);
|
|
|
|
if (unlikely(master->debug_level > 0))
|
|
EC_DBG("Frame size: %i\n", cur_data - frame_data);
|
|
|
|
// Send frame
|
|
ec_device_send(master->device, cur_data - frame_data);
|
|
|
|
if (unlikely(master->debug_level > 0)) {
|
|
end = get_cycles();
|
|
EC_DBG("ec_master_send finished in %ius.\n",
|
|
(u32) (end - start) * 1000 / cpu_khz);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Wertet einen empfangenen Rahmen aus.
|
|
|
|
\return 0 bei Erfolg, sonst < 0
|
|
*/
|
|
|
|
void ec_master_receive(ec_master_t *master, /**< EtherCAT-Master */
|
|
const uint8_t *frame_data, /**< Empfangene Daten */
|
|
size_t size /**< Anzahl empfangene Datenbytes */
|
|
)
|
|
{
|
|
size_t frame_size, data_size;
|
|
uint8_t command_type, command_index;
|
|
unsigned int cmd_follows, matched;
|
|
const uint8_t *cur_data;
|
|
ec_command_t *command;
|
|
|
|
if (unlikely(size < EC_FRAME_HEADER_SIZE)) {
|
|
master->stats.corrupted++;
|
|
ec_master_output_stats(master);
|
|
return;
|
|
}
|
|
|
|
cur_data = frame_data;
|
|
|
|
// Länge des gesamten Frames prüfen
|
|
frame_size = EC_READ_U16(cur_data) & 0x07FF;
|
|
cur_data += EC_FRAME_HEADER_SIZE;
|
|
|
|
if (unlikely(frame_size > size)) {
|
|
master->stats.corrupted++;
|
|
ec_master_output_stats(master);
|
|
return;
|
|
}
|
|
|
|
cmd_follows = 1;
|
|
while (cmd_follows) {
|
|
// Kommando-Header auswerten
|
|
command_type = EC_READ_U8 (cur_data);
|
|
command_index = EC_READ_U8 (cur_data + 1);
|
|
data_size = EC_READ_U16(cur_data + 6) & 0x07FF;
|
|
cmd_follows = EC_READ_U16(cur_data + 6) & 0x8000;
|
|
cur_data += EC_COMMAND_HEADER_SIZE;
|
|
|
|
if (unlikely(cur_data - frame_data
|
|
+ data_size + EC_COMMAND_FOOTER_SIZE > size)) {
|
|
master->stats.corrupted++;
|
|
ec_master_output_stats(master);
|
|
return;
|
|
}
|
|
|
|
// Suche passendes Kommando in der Liste
|
|
matched = 0;
|
|
list_for_each_entry(command, &master->commands, list) {
|
|
if (command->state == EC_CMD_SENT
|
|
&& command->type == command_type
|
|
&& command->index == command_index
|
|
&& command->data_size == data_size) {
|
|
matched = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Kein passendes Kommando in der Liste gefunden
|
|
if (!matched) {
|
|
master->stats.unmatched++;
|
|
ec_master_output_stats(master);
|
|
cur_data += data_size + EC_COMMAND_FOOTER_SIZE;
|
|
continue;
|
|
}
|
|
|
|
// Empfangene Daten in Kommando-Datenspeicher kopieren
|
|
memcpy(command->data, cur_data, data_size);
|
|
cur_data += data_size;
|
|
|
|
// Working-Counter setzen
|
|
command->working_counter = EC_READ_U16(cur_data);
|
|
cur_data += EC_COMMAND_FOOTER_SIZE;
|
|
|
|
// Kommando aus der Liste entfernen
|
|
command->state = EC_CMD_RECEIVED;
|
|
list_del_init(&command->list);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Sendet ein einzelnes Kommando und wartet auf den Empfang.
|
|
|
|
Wenn der Slave nicht antwortet, wird das Kommando
|
|
nochmals gesendet.
|
|
|
|
\return 0 bei Erfolg, sonst < 0
|
|
*/
|
|
|
|
int ec_master_simple_io(ec_master_t *master, /**< EtherCAT-Master */
|
|
ec_command_t *command /**< Kommando */
|
|
)
|
|
{
|
|
unsigned int response_tries_left;
|
|
|
|
response_tries_left = 10;
|
|
do
|
|
{
|
|
ec_master_queue_command(master, command);
|
|
EtherCAT_rt_master_xio(master);
|
|
|
|
if (command->state == EC_CMD_RECEIVED) {
|
|
break;
|
|
}
|
|
else if (command->state == EC_CMD_TIMEOUT) {
|
|
EC_ERR("Simple IO TIMED OUT!\n");
|
|
return -1;
|
|
}
|
|
else if (command->state == EC_CMD_ERROR) {
|
|
EC_ERR("Simple IO command error!\n");
|
|
return -1;
|
|
}
|
|
}
|
|
while (unlikely(!command->working_counter && --response_tries_left));
|
|
|
|
if (unlikely(!response_tries_left)) {
|
|
EC_ERR("No response in simple IO!\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Durchsucht den EtherCAT-Bus nach Slaves.
|
|
|
|
Erstellt ein Array mit allen Slave-Informationen die für den
|
|
weiteren Betrieb notwendig sind.
|
|
|
|
\return 0 bei Erfolg, sonst < 0
|
|
*/
|
|
|
|
int ec_master_bus_scan(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
ec_command_t command;
|
|
ec_slave_t *slave;
|
|
ec_slave_ident_t *ident;
|
|
unsigned int i;
|
|
uint8_t data[2];
|
|
|
|
if (master->slaves || master->slave_count) {
|
|
EC_ERR("Slave scan already done!\n");
|
|
return -1;
|
|
}
|
|
|
|
// Determine number of slaves on bus
|
|
ec_command_init_brd(&command, 0x0000, 4);
|
|
if (unlikely(ec_master_simple_io(master, &command))) return -1;
|
|
master->slave_count = command.working_counter;
|
|
EC_INFO("Found %i slaves on bus.\n", master->slave_count);
|
|
|
|
if (!master->slave_count) return 0;
|
|
|
|
if (!(master->slaves = (ec_slave_t *) kmalloc(master->slave_count
|
|
* sizeof(ec_slave_t),
|
|
GFP_KERNEL))) {
|
|
EC_ERR("Could not allocate memory for slaves!\n");
|
|
return -1;
|
|
}
|
|
|
|
// Init slaves
|
|
for (i = 0; i < master->slave_count; i++) {
|
|
slave = master->slaves + i;
|
|
ec_slave_init(slave, master);
|
|
slave->ring_position = i;
|
|
slave->station_address = i + 1;
|
|
}
|
|
|
|
// For every slave in the list
|
|
for (i = 0; i < master->slave_count; i++)
|
|
{
|
|
slave = master->slaves + i;
|
|
|
|
// Write station address
|
|
EC_WRITE_U16(data, slave->station_address);
|
|
ec_command_init_apwr(&command, slave->ring_position,
|
|
0x0010, sizeof(uint16_t), data);
|
|
if (unlikely(ec_master_simple_io(master, &command))) {
|
|
EC_ERR("Writing station address failed on slave %i!\n", i);
|
|
return -1;
|
|
}
|
|
|
|
// Fetch all slave information
|
|
if (ec_slave_fetch(slave)) return -1;
|
|
|
|
// Search for identification in "database"
|
|
ident = slave_idents;
|
|
while (ident) {
|
|
if (unlikely(ident->vendor_id == slave->sii_vendor_id
|
|
&& ident->product_code == slave->sii_product_code)) {
|
|
slave->type = ident->type;
|
|
break;
|
|
}
|
|
ident++;
|
|
}
|
|
|
|
if (!slave->type)
|
|
EC_WARN("Unknown slave device (vendor 0x%08X, code 0x%08X) at"
|
|
" position %i.\n", slave->sii_vendor_id,
|
|
slave->sii_product_code, i);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Statistik-Ausgaben während des zyklischen Betriebs.
|
|
|
|
Diese Funktion sorgt dafür, dass Statistiken während des zyklischen
|
|
Betriebs bei Bedarf, aber nicht zu oft ausgegeben werden.
|
|
|
|
Die Ausgabe erfolgt gesammelt höchstens einmal pro Sekunde.
|
|
*/
|
|
|
|
void ec_master_output_stats(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
cycles_t t_now = get_cycles();
|
|
|
|
if (unlikely((u32) (t_now - master->stats.t_last) / cpu_khz > 1000)) {
|
|
if (master->stats.timeouts) {
|
|
EC_WARN("%i commands TIMED OUT!\n", master->stats.timeouts);
|
|
master->stats.timeouts = 0;
|
|
}
|
|
if (master->stats.delayed) {
|
|
EC_WARN("%i frame(s) DELAYED!\n", master->stats.delayed);
|
|
master->stats.delayed = 0;
|
|
}
|
|
if (master->stats.corrupted) {
|
|
EC_WARN("%i frame(s) CORRUPTED!\n", master->stats.corrupted);
|
|
master->stats.corrupted = 0;
|
|
}
|
|
if (master->stats.unmatched) {
|
|
EC_WARN("%i command(s) UNMATCHED!\n", master->stats.unmatched);
|
|
master->stats.unmatched = 0;
|
|
}
|
|
master->stats.t_last = t_now;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Wandelt eine ASCII-kodierte Bus-Adresse in einen Slave-Zeiger.
|
|
|
|
Gültige Adress-Strings sind Folgende:
|
|
|
|
- \a "X" = der X. Slave im Bus,
|
|
- \a "X:Y" = der Y. Slave hinter dem X. Buskoppler,
|
|
- \a "#X" = der Slave mit der SSID X,
|
|
- \a "#X:Y" = der Y. Slave hinter dem Buskoppler mit der SSID X.
|
|
|
|
\return Zeiger auf Slave bei Erfolg, sonst NULL
|
|
*/
|
|
|
|
ec_slave_t *ec_master_slave_address(const ec_master_t *master,
|
|
/**< EtherCAT-Master */
|
|
const char *address
|
|
/**< Address-String */
|
|
)
|
|
{
|
|
unsigned long first, second;
|
|
char *remainder, *remainder2;
|
|
unsigned int i;
|
|
int coupler_idx, slave_idx;
|
|
ec_slave_t *slave;
|
|
|
|
if (!address || address[0] == 0) return NULL;
|
|
|
|
if (address[0] == '#') {
|
|
EC_ERR("Bus ID \"%s\" - #<SSID> not implemented yet!\n", address);
|
|
return NULL;
|
|
}
|
|
|
|
first = simple_strtoul(address, &remainder, 0);
|
|
if (remainder == address) {
|
|
EC_ERR("Bus ID \"%s\" - First number empty!\n", address);
|
|
return NULL;
|
|
}
|
|
|
|
if (!remainder[0]) { // absolute position
|
|
if (first < master->slave_count) {
|
|
return master->slaves + first;
|
|
}
|
|
|
|
EC_ERR("Bus ID \"%s\" - Absolute position illegal!\n", address);
|
|
}
|
|
|
|
else if (remainder[0] == ':') { // field position
|
|
|
|
remainder++;
|
|
second = simple_strtoul(remainder, &remainder2, 0);
|
|
|
|
if (remainder2 == remainder) {
|
|
EC_ERR("Bus ID \"%s\" - Sencond number empty!\n", address);
|
|
return NULL;
|
|
}
|
|
|
|
if (remainder2[0]) {
|
|
EC_ERR("Bus ID \"%s\" - Illegal trailer (2)!\n", address);
|
|
return NULL;
|
|
}
|
|
|
|
coupler_idx = -1;
|
|
slave_idx = 0;
|
|
for (i = 0; i < master->slave_count; i++, slave_idx++) {
|
|
slave = master->slaves + i;
|
|
if (!slave->type) continue;
|
|
|
|
if (slave->type->bus_coupler) {
|
|
coupler_idx++;
|
|
slave_idx = 0;
|
|
}
|
|
|
|
if (coupler_idx == first && slave_idx == second) return slave;
|
|
}
|
|
}
|
|
|
|
else
|
|
EC_ERR("Bus ID \"%s\" - Illegal trailer!\n", address);
|
|
|
|
// FIXME ???
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Initialisiert eine Sync-Manager-Konfigurationsseite.
|
|
|
|
Der mit \a data referenzierte Speicher muss mindestens EC_SYNC_SIZE Bytes
|
|
groß sein.
|
|
*/
|
|
|
|
void ec_sync_config(const ec_sync_t *sync, /**< Sync-Manager */
|
|
uint8_t *data /**> Zeiger auf Konfigurationsspeicher */
|
|
)
|
|
{
|
|
EC_WRITE_U16(data, sync->physical_start_address);
|
|
EC_WRITE_U16(data + 2, sync->size);
|
|
EC_WRITE_U8 (data + 4, sync->control_byte);
|
|
EC_WRITE_U8 (data + 5, 0x00); // status byte (read only)
|
|
EC_WRITE_U16(data + 6, 0x0001); // enable
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Initialisiert eine FMMU-Konfigurationsseite.
|
|
|
|
Der mit \a data referenzierte Speicher muss mindestens EC_FMMU_SIZE Bytes
|
|
groß sein.
|
|
*/
|
|
|
|
void ec_fmmu_config(const ec_fmmu_t *fmmu, /**< Sync-Manager */
|
|
uint8_t *data /**> Zeiger auf Konfigurationsspeicher */
|
|
)
|
|
{
|
|
EC_WRITE_U32(data, fmmu->logical_start_address);
|
|
EC_WRITE_U16(data + 4, fmmu->sync->size);
|
|
EC_WRITE_U8 (data + 6, 0x00); // Logical start bit
|
|
EC_WRITE_U8 (data + 7, 0x07); // Logical end bit
|
|
EC_WRITE_U16(data + 8, fmmu->sync->physical_start_address);
|
|
EC_WRITE_U8 (data + 10, 0x00); // Physical start bit
|
|
EC_WRITE_U8 (data + 11, (fmmu->sync->control_byte & 0x04) ? 0x02 : 0x01);
|
|
EC_WRITE_U16(data + 12, 0x0001); // Enable
|
|
EC_WRITE_U16(data + 14, 0x0000); // res.
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Echtzeitschnittstelle
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/**
|
|
Registriert eine neue Domäne.
|
|
|
|
\return Zeiger auf die Domäne bei Erfolg, sonst NULL.
|
|
*/
|
|
|
|
ec_domain_t *EtherCAT_rt_master_register_domain(ec_master_t *master,
|
|
/**< Domäne */
|
|
ec_domain_mode_t mode,
|
|
/**< Modus */
|
|
unsigned int timeout_us
|
|
/**< Timeout */
|
|
)
|
|
{
|
|
ec_domain_t *domain;
|
|
|
|
if (!(domain = (ec_domain_t *) kmalloc(sizeof(ec_domain_t), GFP_KERNEL))) {
|
|
EC_ERR("Error allocating domain memory!\n");
|
|
return NULL;
|
|
}
|
|
|
|
ec_domain_init(domain, master, mode, timeout_us);
|
|
|
|
list_add_tail(&domain->list, &master->domains);
|
|
|
|
return domain;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Konfiguriert alle Slaves und setzt den Operational-Zustand.
|
|
|
|
Führt die komplette Konfiguration und Aktivierunge aller registrierten
|
|
Slaves durch. Setzt Sync-Manager und FMMUs, führt die entsprechenden
|
|
Zustandsübergänge durch, bis der Slave betriebsbereit ist.
|
|
|
|
\return 0 bei Erfolg, sonst < 0
|
|
*/
|
|
|
|
int EtherCAT_rt_master_activate(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
unsigned int i, j;
|
|
ec_slave_t *slave;
|
|
ec_command_t command;
|
|
const ec_sync_t *sync;
|
|
const ec_slave_type_t *type;
|
|
const ec_fmmu_t *fmmu;
|
|
uint8_t data[256];
|
|
uint32_t domain_offset;
|
|
ec_domain_t *domain;
|
|
|
|
// Domains erstellen
|
|
domain_offset = 0;
|
|
list_for_each_entry(domain, &master->domains, list) {
|
|
if (ec_domain_alloc(domain, domain_offset)) {
|
|
EC_ERR("Failed to allocate domain %X!\n", (u32) domain);
|
|
return -1;
|
|
}
|
|
domain_offset += domain->data_size;
|
|
}
|
|
|
|
// Slaves aktivieren
|
|
for (i = 0; i < master->slave_count; i++)
|
|
{
|
|
slave = master->slaves + i;
|
|
|
|
// Change state to INIT
|
|
if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_INIT)))
|
|
return -1;
|
|
|
|
// Check if slave was registered...
|
|
if (!slave->type) {
|
|
EC_WARN("Slave %i has unknown type!\n", i);
|
|
continue;
|
|
}
|
|
|
|
type = slave->type;
|
|
|
|
// Check and reset CRC fault counters
|
|
ec_slave_check_crc(slave);
|
|
|
|
// Resetting FMMUs
|
|
if (slave->base_fmmu_count) {
|
|
memset(data, 0x00, EC_FMMU_SIZE * slave->base_fmmu_count);
|
|
ec_command_init_npwr(&command, slave->station_address, 0x0600,
|
|
EC_FMMU_SIZE * slave->base_fmmu_count, data);
|
|
if (unlikely(ec_master_simple_io(master, &command))) {
|
|
EC_ERR("Resetting FMMUs failed on slave %i!\n",
|
|
slave->ring_position);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Resetting Sync Manager channels
|
|
if (slave->base_sync_count) {
|
|
memset(data, 0x00, EC_SYNC_SIZE * slave->base_sync_count);
|
|
ec_command_init_npwr(&command, slave->station_address, 0x0800,
|
|
EC_SYNC_SIZE * slave->base_sync_count, data);
|
|
if (unlikely(ec_master_simple_io(master, &command))) {
|
|
EC_ERR("Resetting sync managers failed on slave %i!\n",
|
|
slave->ring_position);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Set Sync Managers
|
|
for (j = 0; type->sync_managers[j] && j < EC_MAX_SYNC; j++)
|
|
{
|
|
sync = type->sync_managers[j];
|
|
|
|
ec_sync_config(sync, data);
|
|
ec_command_init_npwr(&command, slave->station_address,
|
|
0x0800 + j * EC_SYNC_SIZE, EC_SYNC_SIZE,
|
|
data);
|
|
if (unlikely(ec_master_simple_io(master, &command))) {
|
|
EC_ERR("Setting sync manager %i failed on slave %i!\n",
|
|
j, slave->ring_position);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Change state to PREOP
|
|
if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_PREOP)))
|
|
return -1;
|
|
|
|
// Slaves that are not registered are only brought into PREOP
|
|
// state -> nice blinking and mailbox comm. possible
|
|
if (!slave->registered && !slave->type->bus_coupler) {
|
|
EC_WARN("Slave %i was not registered!\n", slave->ring_position);
|
|
continue;
|
|
}
|
|
|
|
// Set FMMUs
|
|
for (j = 0; j < slave->fmmu_count; j++)
|
|
{
|
|
fmmu = &slave->fmmus[j];
|
|
|
|
ec_fmmu_config(fmmu, data);
|
|
ec_command_init_npwr(&command, slave->station_address,
|
|
0x0600 + j * EC_FMMU_SIZE, EC_FMMU_SIZE,
|
|
data);
|
|
if (unlikely(ec_master_simple_io(master, &command))) {
|
|
EC_ERR("Setting FMMU %i failed on slave %i!\n",
|
|
j, slave->ring_position);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Change state to SAVEOP
|
|
if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_SAVEOP)))
|
|
return -1;
|
|
|
|
// Change state to OP
|
|
if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_OP)))
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Setzt alle Slaves zurück in den Init-Zustand.
|
|
|
|
\return 0 bei Erfolg, sonst < 0
|
|
*/
|
|
|
|
int EtherCAT_rt_master_deactivate(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
ec_slave_t *slave;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < master->slave_count; i++)
|
|
{
|
|
slave = master->slaves + i;
|
|
|
|
// CRC-Zählerstände ausgeben
|
|
ec_slave_check_crc(slave);
|
|
|
|
if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_INIT) != 0))
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Sendet und empfängt Kommandos.
|
|
|
|
\return 0 bei Erfolg, sonst < 0
|
|
*/
|
|
|
|
void EtherCAT_rt_master_xio(ec_master_t *master)
|
|
{
|
|
ec_command_t *command, *next;
|
|
unsigned int commands_sent;
|
|
cycles_t t_start, t_end, t_timeout;
|
|
|
|
ec_master_output_stats(master);
|
|
|
|
if (unlikely(!master->device->link_state)) {
|
|
// Link DOWN, keines der Kommandos kann gesendet werden.
|
|
list_for_each_entry_safe(command, next, &master->commands, list) {
|
|
command->state = EC_CMD_ERROR;
|
|
list_del_init(&command->list);
|
|
}
|
|
|
|
// Device-Zustand abfragen
|
|
ec_device_call_isr(master->device);
|
|
return;
|
|
}
|
|
|
|
// Rahmen senden
|
|
ec_master_send_commands(master);
|
|
|
|
t_start = get_cycles(); // Sendezeit nehmen
|
|
t_timeout = 100 * cpu_khz / 1000; // 100us
|
|
|
|
do {
|
|
ec_device_call_isr(master->device);
|
|
|
|
t_end = get_cycles(); // Aktuelle Zeit nehmen
|
|
if (t_end - t_start >= t_timeout) break; // Timeout
|
|
|
|
commands_sent = 0;
|
|
list_for_each_entry_safe(command, next, &master->commands, list) {
|
|
if (command->state == EC_CMD_RECEIVED)
|
|
list_del_init(&command->list);
|
|
else if (command->state == EC_CMD_SENT)
|
|
commands_sent++;
|
|
}
|
|
} while (commands_sent);
|
|
|
|
// Zeit abgelaufen. Alle verbleibenden Kommandos entfernen.
|
|
list_for_each_entry_safe(command, next, &master->commands, list) {
|
|
switch (command->state) {
|
|
case EC_CMD_SENT:
|
|
case EC_CMD_QUEUED:
|
|
command->state = EC_CMD_TIMEOUT;
|
|
master->stats.timeouts++;
|
|
ec_master_output_stats(master);
|
|
break;
|
|
case EC_CMD_RECEIVED:
|
|
master->stats.delayed++;
|
|
ec_master_output_stats(master);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
list_del_init(&command->list);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Setzt die Debug-Ebene des Masters.
|
|
|
|
Folgende Debug-Level sind definiert:
|
|
|
|
- 1: Nur Positionsmarken in bestimmten Funktionen
|
|
- 2: Komplette Frame-Inhalte
|
|
*/
|
|
|
|
void EtherCAT_rt_master_debug(ec_master_t *master,
|
|
/**< EtherCAT-Master */
|
|
int level
|
|
/**< Debug-Level */
|
|
)
|
|
{
|
|
if (level != master->debug_level) {
|
|
master->debug_level = level;
|
|
EC_INFO("Master debug level set to %i.\n", level);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Gibt alle Informationen zum Master aus.
|
|
*/
|
|
|
|
void EtherCAT_rt_master_print(const ec_master_t *master
|
|
/**< EtherCAT-Master */
|
|
)
|
|
{
|
|
unsigned int i;
|
|
|
|
EC_INFO("*** Begin master information ***\n");
|
|
for (i = 0; i < master->slave_count; i++)
|
|
ec_slave_print(&master->slaves[i]);
|
|
EC_INFO("*** End master information ***\n");
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
EXPORT_SYMBOL(EtherCAT_rt_master_register_domain);
|
|
EXPORT_SYMBOL(EtherCAT_rt_master_activate);
|
|
EXPORT_SYMBOL(EtherCAT_rt_master_deactivate);
|
|
EXPORT_SYMBOL(EtherCAT_rt_master_xio);
|
|
EXPORT_SYMBOL(EtherCAT_rt_master_debug);
|
|
EXPORT_SYMBOL(EtherCAT_rt_master_print);
|
|
|
|
/*****************************************************************************/
|
|
|
|
/* Emacs-Konfiguration
|
|
;;; Local Variables: ***
|
|
;;; c-basic-offset:4 ***
|
|
;;; End: ***
|
|
*/
|