MCU.TEC C,Padrões de Projetos Padrões de Projeto para Sistemas Embarcados em C

Padrões de Projeto para Sistemas Embarcados em C


Os padrões de projeto são soluções reutilizáveis para problemas recorrentes no desenvolvimento de software. Em sistemas embarcados, onde recursos como memória e processamento são limitados, escolher a estrutura correta pode ser decisivo para o sucesso do projeto.

Neste artigo, iniciamos uma série de artigos sobre padrões de projeto aplicáveis ao desenvolvimento em C para microcontroladores. Para cada padrão, descreveremos seu propósito, vantagens e um cenário de aplicação. Cada um desses padrões será tratado com mais profundidade em artigos separados ao longo desta série.

Este material tem como base os seguintes livros de referência:

  • Design Patterns for Embedded Systems in C de Bruce Powel Douglass
  • Real-Time Design Patterns: Robust Scalable Architecture for Real-Time Systems de Bruce Powel Douglass
  • Patterns of Enterprise Application Architecture de Martin Fowler
  • Domain-Driven Design de Eric Evans
  • Design Patterns: Elements of Reusable Object-Oriented Software de Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides (Gang of Four)


Minha Jornada com Padrões de Projeto em C

Quando comecei a programar em Java, há 20 anos, fiquei fascinado com a capacidade da linguagem de representar objetos em forma de código. Isso me levou a buscar mais conhecimento sobre o assunto, e logo me deparei com livros como Domain-Driven Design de Eric Evans, Patterns of Enterprise Application Architecture de Martin Fowler e Design Patterns. O entendimento desses padrões de projeto abriu minha mente para a importância de desenvolver código altamente estruturado e de qualidade.

Entretanto, nunca havia considerado que no contexto procedural da linguagem C também pudessem existir tais padrões. Para minha surpresa, em uma conversa recente com meu tutor na Embarcatech, fui apresentado ao conceito de Orientação a Objetos em C. Não estou falando de C++, mas sim de C puro! Isso me despertou uma enorme curiosidade, e iniciei uma pesquisa sobre o tema. Foi então que me deparei com os livros de Bruce Powel Douglass sobre padrões de projeto em sistemas embarcados.

Ainda não tive meu boooommmm de entendimento total no contexto de microcontroladores, pois não consegui ler os livros por completo. No entanto, como tudo que estudo vira material para meus sites e livros (um dia eles saem!), decidi abordar um padrão de projeto por mês e compartilhar essas descobertas com vocês.


Padrões de Projeto para Sistemas Embarcados

Aqui está uma lista abrangente dos principais padrões de projeto aplicáveis ao desenvolvimento de software para microcontroladores e sistemas embarcados. Eles estão organizados por categoria para facilitar a compreensão:

1. Padrões de Arquitetura de Subsistema e Componentes

Os padrões desta categoria ajudam a estruturar o software embarcado de forma modular, facilitando a manutenção, a escalabilidade e a reutilização de código. Eles definem como os diferentes módulos do sistema interagem entre si e organizam a arquitetura geral do software.

1.1 Layered Pattern (Padrão em Camadas)

  • Problema que resolve: Mistura de responsabilidades dentro do código, dificultando a manutenção e a reutilização.
  • Cenário de uso: Projetos que exigem separação clara entre hardware, drivers, middleware e aplicação.
  • Vantagens: Organização do código, maior reutilização de componentes e facilidade de manutenção.
  • O que é: Divide o sistema em camadas hierárquicas onde cada uma depende apenas da camada inferior, promovendo um código mais organizado e de fácil escalabilidade.

1.2 Microkernel Architecture Pattern

  • Problema que resolve: Sistemas grandes e complexos com funcionalidades variáveis que precisam ser ativadas ou desativadas dinamicamente.
  • Cenário de uso: Sistemas embarcados modulares que precisam suportar diferentes configurações de hardware ou software.
  • Vantagens: Melhor gerenciamento de recursos e flexibilidade na ativação/desativação de módulos.
  • O que é: Mantém um núcleo mínimo do sistema (microkernel) e permite adicionar funcionalidades por meio de módulos externos.

1.3 Component-Based Architecture Pattern

  • Problema que resolve: Alto acoplamento entre módulos, dificultando a reutilização e a substituição de componentes.
  • Cenário de uso: Sistemas embarcados que precisam de alta modularidade, como IoT e automação industrial.
  • Vantagens: Facilita a manutenção e a atualização do sistema sem afetar outros componentes.
  • O que é: Estrutura o software em componentes independentes que interagem por meio de interfaces bem definidas.

1.4 Virtual Machine Pattern

  • Problema que resolve: Dificuldade em portar software para diferentes plataformas de hardware.
  • Cenário de uso: Sistemas embarcados que precisam rodar em múltiplos tipos de hardware sem grandes adaptações no código.
  • Vantagens: Independência de hardware e reutilização de código entre diferentes plataformas.
  • O que é: Cria uma camada de abstração que simula um ambiente de execução padronizado, permitindo que o software funcione de forma mais genérica.

1.5 Hierarchical Control Pattern

  • Problema que resolve: Falta de organização em sistemas embarcados que possuem múltiplos níveis de controle.
  • Cenário de uso: Sistemas que exigem tomada de decisão em diferentes níveis, como controle de motores e automação.
  • Vantagens: Melhor separação de responsabilidades e hierarquia clara no fluxo de controle.
  • O que é: Divide o controle do sistema em diferentes níveis hierárquicos, permitindo um gerenciamento mais eficiente e modular.

1.6 Recursive Containment Pattern

  • Problema que resolve: Complexidade na organização de subsistemas que possuem componentes recursivos ou aninhados.
  • Cenário de uso: Sistemas embarcados que possuem módulos que precisam ser tratados como subsistemas independentes.
  • Vantagens: Organização modular clara e facilidade na expansão do sistema.
  • O que é: Estrutura os componentes do sistema de maneira hierárquica e recursiva, onde cada elemento pode conter outros elementos semelhantes.

1.7 ROOM Pattern (Real-Time Object-Oriented Modeling)

O que é: Um padrão que fornece uma abordagem orientada a objetos para modelagem de sistemas de tempo real.

Problema que resolve: Falta de um modelo estruturado para desenvolvimento orientado a objetos em sistemas embarcados.

Cenário de uso: Sistemas de tempo real que precisam seguir um modelo de desenvolvimento baseado em objetos.

Vantagens: Modelagem bem definida, facilitando o desenvolvimento e manutenção do sistema.

2. Padrões de Concorrência

Os padrões desta categoria lidam com a execução de múltiplas tarefas em sistemas embarcados, garantindo que processos concorrentes operem de maneira eficiente e segura. Eles ajudam a evitar condições de corrida, otimizar a comunicação entre tarefas e melhorar a previsibilidade do sistema.

2.1 Message Queuing Pattern

  • Problema que resolve: Concorrência e sincronização inadequada entre diferentes tarefas que compartilham dados.
  • Cenário de uso: Sistemas embarcados multitarefa onde tarefas precisam trocar informações de forma controlada.
  • Vantagens: Evita condições de corrida e melhora a organização da comunicação entre tarefas.
  • O que é: Utiliza filas de mensagens para permitir a comunicação assíncrona entre tarefas de forma controlada.

2.2 Interrupt Pattern

  • Problema que resolve: Latência alta ao lidar com eventos externos devido a verificações contínuas (polling).
  • Cenário de uso: Sistemas que precisam responder rapidamente a eventos externos, como acionamento de sensores.
  • Vantagens: Reduz consumo de CPU, melhora tempo de resposta e eficiência energética.
  • O que é: Utiliza interrupções de hardware para lidar com eventos externos de forma imediata, evitando verificações constantes.

2.3 Guarded Call Pattern

  • Problema que resolve: Concorrência inadequada e acesso simultâneo a recursos compartilhados.
  • Cenário de uso: Sistemas embarcados multitarefa que precisam garantir a execução segura de chamadas a funções compartilhadas.
  • Vantagens: Previne corrupção de dados e melhora a confiabilidade do sistema.
  • O que é: Implementa verificações e sincronização para garantir que chamadas a funções críticas sejam executadas apenas quando seguras.

2.4 Rendezvous Pattern

  • Problema que resolve: Sincronização inadequada entre duas ou mais tarefas que precisam se comunicar diretamente.
  • Cenário de uso: Sistemas onde duas tarefas dependem do estado da outra para avançar, como protocolos de comunicação.
  • Vantagens: Melhora a coordenação entre tarefas e evita espera passiva.
  • O que é: Estabelece um mecanismo onde duas tarefas precisam estar prontas simultaneamente para prosseguir com a comunicação.

2.5 Cyclic Executive Pattern

  • Problema que resolve: Complexidade e overhead na gestão de múltiplas tarefas concorrentes.
  • Cenário de uso: Sistemas embarcados simples sem RTOS, onde tarefas precisam ser executadas periodicamente.
  • Vantagens: Controle determinístico e fácil implementação.
  • O que é: Um loop fixo que executa cada tarefa em uma ordem predefinida dentro de um ciclo fixo de tempo.

2.6 Round Robin Pattern

  • Problema que resolve: Falta de tempo de CPU equitativo entre tarefas concorrentes.
  • Cenário de uso: Sistemas embarcados com multitarefa onde cada processo deve receber um tempo justo de execução.
  • Vantagens: Distribuição justa de CPU entre tarefas, evitando monopolização do processador.
  • O que é: Um mecanismo de escalonamento que alterna a execução de cada tarefa por um tempo determinado, em um ciclo contínuo.

2.7 Static Priority Pattern

  • Problema que resolve: Falta de controle sobre a execução de tarefas críticas em tempo real.
  • Cenário de uso: Sistemas de tempo real que exigem resposta imediata para determinadas tarefas prioritárias.
  • Vantagens: Garante que tarefas críticas sejam executadas antes das menos prioritárias.
  • O que é: Define prioridades fixas para cada tarefa, garantindo que aquelas com maior prioridade sempre sejam executadas primeiro.

2.8 Dynamic Priority Pattern

  • Problema que resolve: Problemas de bloqueios ou inversão de prioridade em sistemas multitarefa.
  • Cenário de uso: Sistemas onde a prioridade das tarefas pode mudar com base na carga de trabalho.
  • Vantagens: Melhora o balanceamento de carga do processador e evita deadlocks.
  • O que é: Ajusta dinamicamente as prioridades das tarefas conforme necessário para garantir melhor desempenho e tempo de resposta.

3. Padrões de Gerenciamento de Memória

Os padrões desta categoria ajudam a otimizar o uso da memória em sistemas embarcados, onde os recursos são frequentemente limitados. Eles garantem que a alocação e a liberação de memória ocorram de forma eficiente, evitando desperdícios e garantindo previsibilidade no consumo de RAM.

3.1 Static Allocation Pattern

  • Problema que resolve: Uso imprevisível de memória devido a alocações dinâmicas, que podem causar fragmentação e falhas em sistemas embarcados críticos.
  • Cenário de uso: Sistemas embarcados com recursos limitados que precisam de controle total sobre a alocação de memória.
  • Vantagens: Evita fragmentação da memória e melhora a previsibilidade do uso de RAM.
  • O que é: Utiliza apenas alocações estáticas, definindo variáveis globais e buffers fixos durante a compilação.

3.2 Pool Allocation Pattern

  • Problema que resolve: Ineficiência e fragmentação de memória ao alocar e desalocar pequenos blocos dinamicamente.
  • Cenário de uso: Sistemas que precisam gerenciar dinamicamente a alocação de pequenos blocos de memória sem risco de fragmentação.
  • Vantagens: Reduz fragmentação e melhora a previsibilidade no tempo de acesso à memória.
  • O que é: Aloca um conjunto fixo de blocos de memória e os gerencia em um pool pré-definido, evitando alocações e desalocações imprevisíveis.

3.3 Fixed-Sized Buffer Pattern

  • Problema que resolve: Overhead e fragmentação ao lidar com buffers de tamanho variável.
  • Cenário de uso: Aplicações que precisam de buffers para comunicação ou armazenamento temporário de dados.
  • Vantagens: Maior eficiência no gerenciamento de memória e redução de falhas associadas a alocações dinâmicas.
  • O que é: Define buffers de tamanho fixo e pré-alocados para operações específicas, garantindo previsibilidade e eficiência no uso da memória.

3.4 Smart Pointer Pattern

  • Problema que resolve: Vazamento de memória devido a alocações dinâmicas sem liberação adequada.
  • Cenário de uso: Sistemas embarcados que precisam gerenciar dinamicamente a memória sem risco de vazamentos.
  • Vantagens: Reduz a necessidade de gerenciar manualmente a liberação de memória, evitando erros de alocação.
  • O que é: Implementa ponteiros inteligentes que controlam automaticamente a alocação e liberação da memória associada.

3.5 Garbage Collection Pattern

  • Problema que resolve: Necessidade de liberação manual de memória em sistemas que fazem uso intenso de alocação dinâmica.
  • Cenário de uso: Aplicações complexas que utilizam alocação dinâmica e exigem gerenciamento automatizado da memória.
  • Vantagens: Libera automaticamente memória não utilizada, reduzindo erros de alocação e melhorando a estabilidade do sistema.
  • O que é: Implementa um mecanismo que monitora e libera memória não referenciada automaticamente.

3.6 Garbage Compactor Pattern

  • Problema que resolve: Fragmentação de memória ao longo do tempo devido a alocações e desalocações dinâmicas.
  • Cenário de uso: Sistemas embarcados com uso dinâmico de memória onde a fragmentação pode comprometer a alocação de novos blocos.
  • Vantagens: Reduz fragmentação de memória e melhora a eficiência da alocação.
  • O que é: Reorganiza a memória alocada periodicamente para eliminar fragmentação e garantir blocos contíguos livres.

4. Padrões de Gerenciamento de Recursos

Os padrões desta categoria garantem o acesso eficiente e seguro a recursos compartilhados em sistemas embarcados. Eles ajudam a evitar problemas como deadlocks, inversão de prioridade e acesso concorrente inadequado a periféricos e variáveis globais.

4.1 Critical Section Pattern

  • Problema que resolve: Condições de corrida causadas por múltiplas tarefas acessando simultaneamente um mesmo recurso.
  • Cenário de uso: Sistemas multitarefa onde variáveis globais ou periféricos precisam ser acessados por diferentes processos.
  • Vantagens: Evita corrupção de dados e garante acesso controlado aos recursos compartilhados.
  • O que é: Implementa seções críticas onde apenas uma tarefa pode acessar um recurso por vez, utilizando mutexes, semáforos ou desativação de interrupções.

4.2 Priority Inheritance Pattern

  • Problema que resolve: Inversão de prioridade, onde uma tarefa de baixa prioridade impede a execução de uma tarefa crítica.
  • Cenário de uso: Sistemas de tempo real onde tarefas críticas podem ser bloqueadas por tarefas de menor prioridade.
  • Vantagens: Garante que tarefas de alta prioridade não fiquem bloqueadas desnecessariamente.
  • O que é: Permite que uma tarefa de baixa prioridade herde temporariamente a prioridade de uma tarefa de maior prioridade até liberar o recurso.

4.3 Highest Locker Pattern

  • Problema que resolve: Deadlocks gerados por múltiplas tarefas bloqueando recursos de forma desordenada.
  • Cenário de uso: Sistemas onde várias tarefas competem por múltiplos recursos compartilhados.
  • Vantagens: Evita deadlocks e melhora a previsibilidade do sistema.
  • O que é: Assegura que apenas a tarefa com a maior prioridade entre as que precisam do recurso possa bloqueá-lo, forçando uma ordem de acesso previsível.

4.4 Priority Ceiling Pattern

  • Problema que resolve: Inversão de prioridade e bloqueios inesperados devido a compartilhamento inadequado de recursos.
  • Cenário de uso: Sistemas embarcados críticos onde múltiplas tarefas acessam o mesmo recurso.
  • Vantagens: Evita bloqueios imprevisíveis e melhora o tempo de resposta das tarefas críticas.
  • O que é: Define uma prioridade máxima para cada recurso compartilhado, garantindo que apenas tarefas com prioridade igual ou superior possam acessá-lo.

4.5 Simultaneous Locking Pattern

  • Problema que resolve: Deadlocks e ineficiência ao acessar múltiplos recursos compartilhados ao mesmo tempo.
  • Cenário de uso: Sistemas onde múltiplas tarefas precisam acessar vários recursos simultaneamente.
  • Vantagens: Reduz o risco de deadlocks e melhora a eficiência no gerenciamento de recursos.
  • O que é: Implementa um mecanismo de bloqueio simultâneo de todos os recursos necessários antes de iniciar uma operação crítica.

4.6 Ordered Locking Pattern

  • Problema que resolve: Deadlocks causados por ordem inconsistente de bloqueio de recursos.
  • Cenário de uso: Sistemas multitarefa onde diferentes processos podem acessar recursos em ordens variadas.
  • Vantagens: Previne deadlocks sem necessidade de mecanismos complexos de verificação.
  • O que é: Impõe uma ordem fixa de bloqueio de recursos, garantindo que todas as tarefas sigam a mesma sequência ao acessá-los.

5. Padrões de Distribuição e Comunicação

Os padrões desta categoria garantem uma comunicação eficiente entre diferentes partes do sistema, seja dentro de um único microcontrolador ou entre múltiplos dispositivos conectados em rede. Eles ajudam a estruturar a troca de informações de forma confiável e escalável.

5.1 Shared Memory Pattern

  • Problema que resolve: Comunicação ineficiente entre tarefas devido ao uso de filas ou mensagens que geram sobrecarga.
  • Cenário de uso: Sistemas embarcados que precisam compartilhar grandes volumes de dados entre múltiplas tarefas ou processadores.
  • Vantagens: Baixa latência e alta taxa de transferência de dados.
  • O que é: Um espaço de memória compartilhado é utilizado como meio de comunicação entre processos, evitando a necessidade de cópias de dados desnecessárias.

5.2 Remote Method Call Pattern

  • Problema que resolve: Comunicação entre processos distribuídos sem uma interface de baixo nível bem definida.
  • Cenário de uso: Sistemas embarcados que se comunicam via redes como CAN, Modbus ou Ethernet.
  • Vantagens: Oculta a complexidade da comunicação e facilita a implementação de chamadas remotas.
  • O que é: Permite que um processo invoque funções em outro sistema remoto como se fossem chamadas locais.

5.3 Observer Pattern

  • Problema que resolve: Comunicação ineficiente entre módulos que precisam ser notificados sobre mudanças de estado.
  • Cenário de uso: Sistemas de eventos, sensores e notificações em tempo real.
  • Vantagens: Reduz o acoplamento entre os módulos e melhora a escalabilidade do sistema.
  • O que é: Define um mecanismo de notificação assíncrona onde múltiplos observadores podem ser informados sobre mudanças de estado de um único sujeito.

5.4 Data Bus Pattern

  • Problema que resolve: Comunicação complexa e desorganizada entre múltiplos módulos do sistema.
  • Cenário de uso: Arquiteturas de sistemas embarcados que exigem comunicação padronizada entre diversos componentes.
  • Vantagens: Centraliza a comunicação, reduz a complexidade e melhora a modularidade.
  • O que é: Implementa um barramento de dados onde múltiplos componentes podem publicar e assinar mensagens de forma padronizada.

5.5 Proxy Pattern

  • Problema que resolve: Comunicação ineficiente e insegura entre componentes distribuídos ou remotos.
  • Cenário de uso: Sistemas embarcados que utilizam interfaces de comunicação externas ou protocolos remotos.
  • Vantagens: Melhora a segurança, reduz a latência e otimiza a comunicação entre módulos separados.
  • O que é: Um intermediário que atua como representante de outro objeto, fornecendo uma interface simplificada e segura para acessá-lo.

5.6 Broker Pattern

  • Problema que resolve: Dependência direta entre módulos que precisam trocar informações de forma distribuída.
  • Cenário de uso: Sistemas de comunicação assíncrona, como IoT e redes industriais.
  • Vantagens: Desacopla os módulos e permite escalabilidade na comunicação.
  • O que é: Introduz um broker (intermediário) que gerencia a comunicação entre diferentes partes do sistema sem que elas precisem conhecer diretamente umas às outras.

6. Padrões de Segurança e Confiabilidade

Os padrões desta categoria garantem que o sistema continue operando de forma segura e confiável mesmo diante de falhas de hardware, software ou interferências externas. Eles são especialmente importantes em sistemas críticos, como automação industrial, aeroespacial e dispositivos médicos.

6.1 Protected Single Channel Pattern

  • Problema que resolve: Falhas na comunicação entre módulos devido a corrupção de dados.
  • Cenário de uso: Sistemas que transmitem dados críticos via barramentos compartilhados.
  • Vantagens: Garante a integridade dos dados transmitidos.
  • O que é: Implementa mecanismos de verificação, como CRC e checksum, para assegurar que as mensagens recebidas sejam íntegras.

6.2 Homogeneous Redundancy Pattern

  • Problema que resolve: Falhas em componentes críticos sem um sistema de backup imediato.
  • Cenário de uso: Sistemas embarcados de missão crítica, como aeronaves e equipamentos médicos.
  • Vantagens: Aumenta a confiabilidade do sistema ao replicar funcionalidades críticas.
  • O que é: Usa múltiplas instâncias idênticas de um componente para fornecer redundância e permitir recuperação automática em caso de falha.

6.3 Triple Modular Redundancy (TMR) Pattern

  • Problema que resolve: Erros transitórios ou permanentes em hardware crítico.
  • Cenário de uso: Sistemas de controle de segurança, como sensores de usinas nucleares e controle de motores em aeronaves.
  • Vantagens: Aumenta a resiliência do sistema contra falhas de hardware.
  • O que é: Utiliza três módulos independentes executando a mesma tarefa e um votador que decide o resultado correto com base no consenso entre os três.

6.4 Heterogeneous Redundancy Pattern

  • Problema que resolve: Falhas que afetam sistemas idênticos devido a vulnerabilidades comuns.
  • Cenário de uso: Aplicações onde falhas sistêmicas devem ser evitadas a todo custo.
  • Vantagens: Maior robustez contra falhas comuns.
  • O que é: Usa diferentes implementações de um mesmo sistema para reduzir o risco de falhas sistêmicas.

6.5 Monitor-Actuator Pattern

  • Problema que resolve: Falta de monitoramento automático para detectar falhas antes que causem danos ao sistema.
  • Cenário de uso: Sistemas que precisam garantir operação contínua sem intervenção humana constante.
  • Vantagens: Aumenta a resiliência do sistema.
  • O que é: Implementa sensores e atuadores que monitoram o estado do sistema e tomam ações preventivas quando necessário.

6.6 Sanity Check Pattern

  • Problema que resolve: Comportamento imprevisível do sistema devido a erros internos ou falhas não detectadas.
  • Cenário de uso: Sistemas embarcados que precisam garantir um funcionamento previsível e seguro.
  • Vantagens: Detecta erros rapidamente e evita falhas catastróficas.
  • O que é: Implementa verificações automáticas que garantem que o sistema esteja operando dentro dos parâmetros esperados.

6.7 Watchdog Pattern

  • Problema que resolve: Travamento do sistema devido a loops infinitos ou falhas inesperadas.
  • Cenário de uso: Qualquer sistema embarcado onde a operação contínua deve ser garantida.
  • Vantagens: Melhora a estabilidade e recuperação do sistema.
  • O que é: Utiliza um temporizador de supervisão (watchdog) que reinicia o sistema se uma falha for detectada.

6.8 Safety Executive Pattern

  • Problema que resolve: Falha na tomada de decisões de segurança em sistemas críticos.
  • Cenário de uso: Sistemas de segurança industrial e dispositivos médicos.
  • Vantagens: Melhora a confiabilidade e a resposta a falhas.
  • O que é: Implementa um mecanismo de supervisão que avalia continuamente os riscos e toma ações para evitar falhas graves.

7. Conclusão

Os padrões de projeto desempenham um papel fundamental no desenvolvimento de sistemas embarcados eficientes, seguros e escaláveis. Cada categoria de padrões abordada neste artigo fornece soluções estruturadas para problemas recorrentes, desde a organização da arquitetura até o gerenciamento de concorrência, memória, recursos, comunicação e segurança.

A escolha adequada dos padrões a serem utilizados em um projeto pode impactar diretamente na modularidade, desempenho e confiabilidade do sistema embarcado. Implementar esses padrões corretamente pode reduzir a complexidade do código, melhorar a manutenibilidade e garantir que o software funcione de forma previsível, mesmo em cenários críticos.

Nos próximos artigos desta série, exploraremos cada padrão em detalhes, apresentando exemplos práticos e sua aplicação em C para sistemas embarcados. Assim, engenheiros e desenvolvedores poderão aplicar esses conceitos de maneira eficaz em seus projetos.

5 1 voto
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

0
Adoraria saber sua opinião, comente.x