#include <bits/stdc++.h>
using namespace std;
const long long int mod = 1000000007;
const long long int INF = 1e18;
const long double PI = 3.14159265358979323;
long long int N, M, K, A, B = 0, res = 0, R = 0, G = 0, H, W;
long long int minA = INF, minB = INF;
string S[25];
class Graph {
 public:
  vector<pair<long long int, long long int> > *Edge;
  vector<pair<pair<long long int, long long int>, long long int> > PrimEdge;
  long long int *InEdge;
  long long int MaxV, MaxE;
  long long int *BellmanFordResult, *DijkstraResult, **WarshallFloydResult;
  vector<long long int> TopologicalSortdVertex;
  bool *InfiniteUpdate;
  bool NegativeLoop = false;
  long long int PrimResult = 0;
  bool NotConected = false;
  Graph(long long int InputV, long long int InputE) {
    MaxV = InputV;
    MaxE = InputE;
    Edge = new vector<pair<long long int, long long int> >[InputV + 1];
  }
  void BellmanFord(long long int Start) {
    BellmanFordResult = new long long int[MaxV];
    InfiniteUpdate = new bool[MaxV];
    for (int i = 0; i < MaxV; i++) {
      BellmanFordResult[i] = INF;
      InfiniteUpdate[i] = false;
    }
    BellmanFordResult[Start] = 0;
    long long int turn = 0;
    while (true) {
      turn++;
      bool update = false;
      for (int i = 0; i < MaxV; i++) {
        for (int j = 0; j < Edge[i].size(); j++) {
          pair<long long int, long long int> e = Edge[i][j];
          if (turn >= MaxV && InfiniteUpdate[i]) {
            InfiniteUpdate[e.first] = true;
          }
          if (BellmanFordResult[i] != INF &&
              BellmanFordResult[e.first] > BellmanFordResult[i] + e.second) {
            BellmanFordResult[e.first] = BellmanFordResult[i] + e.second;
            update = true;
            if (turn >= MaxV) {
              InfiniteUpdate[e.first] = true;
            }
          }
        }
      }
      if (!update) {
        NegativeLoop = false;
        break;
      }
      if (turn >= 2 * MaxV - 1) {
        NegativeLoop = true;
        break;
      }
    }
  }
  void Dijkstra(long long int Start) {
    DijkstraResult = new long long int[MaxV];
    priority_queue<pair<long long int, long long int>,
                   vector<pair<long long int, long long int> >,
                   greater<pair<long long int, long long int> > >
        que;
    fill(DijkstraResult, DijkstraResult + MaxV, INF);
    DijkstraResult[Start] = 0;
    que.push(pair<long long int, long long int>(0, Start));
    while (!que.empty()) {
      pair<long long int, long long int> p = que.top();
      que.pop();
      long long int v = p.second;
      if (DijkstraResult[v] < p.first) continue;
      for (int i = 0; i < Edge[v].size(); i++) {
        pair<long long int, long long int> e = Edge[v][i];
        if (DijkstraResult[e.first] > DijkstraResult[v] + e.second) {
          DijkstraResult[e.first] = DijkstraResult[v] + e.second;
          que.push(pair<long long int, long long int>(DijkstraResult[e.first],
                                                      e.first));
        }
      }
    }
  }
  void WarshallFloyd() {
    WarshallFloydResult = new long long int *[MaxV];
    for (int i = 0; i < MaxV; i++) {
      WarshallFloydResult[i] = new long long int[MaxV];
    }
    for (long long int i = 0; i < MaxV; i++) {
      for (long long int j = 0; j < MaxV; j++) {
        WarshallFloydResult[i][j] = INF;
      }
    }
    for (int i = 0; i < MaxV; i++) {
      for (int j = 0; j < Edge[i].size(); j++) {
        WarshallFloydResult[i][Edge[i][j].first] = Edge[i][j].second;
      }
    }
    for (int k = 0; k < MaxV; k++) {
      for (int i = 0; i < MaxV; i++) {
        for (int j = 0; j < MaxV; j++) {
          WarshallFloydResult[i][j] =
              min(WarshallFloydResult[i][j],
                  WarshallFloydResult[i][k] + WarshallFloydResult[k][j]);
        }
      }
    }
  }
  void Prim() {
    NotConected = false;
    priority_queue<
        pair<long long int, pair<long long int, long long int> >,
        vector<pair<long long int, pair<long long int, long long int> > >,
        greater<pair<long long int, pair<long long int, long long int> > > >
        PriorityEdge;
    bool *IncludedVertex = new bool[MaxV];
    fill(IncludedVertex, IncludedVertex + MaxV, false);
    PrimEdge.clear();
    for (int i = 0; i < Edge[0].size(); i++) {
      PriorityEdge.push({Edge[0][i].second, {0, Edge[0][i].first}});
    }
    IncludedVertex[0] = true;
    for (int i = 0; i < MaxV - 1; i++) {
      while (1) {
        if (PriorityEdge.empty()) {
          NotConected = true;
          break;
        }
        pair<long long int, pair<long long int, long long int> > pe =
            PriorityEdge.top();
        PriorityEdge.pop();
        if (!IncludedVertex[pe.second.second]) {
          IncludedVertex[pe.second.second] = true;
          PrimResult += pe.first;
          PrimEdge.push_back({pe.second, pe.first});
          for (int j = 0; j < Edge[pe.second.second].size(); j++) {
            PriorityEdge.push(
                {Edge[pe.second.second][j].second,
                 {pe.second.second, Edge[pe.second.second][j].first}});
          }
          break;
        }
      }
    }
  }
  void TopologicalSort() {
    bool notupdate = false;
    InEdge = new long long int[MaxV];
    TopologicalSortdVertex.clear();
    queue<long long int> task, firsttask;
    for (int i = 0; i < MaxV; i++) {
      InEdge[i] = 0;
    }
    for (int i = 0; i < MaxV; i++) {
      for (int j = 0; j < Edge[i].size(); j++) {
        InEdge[Edge[i][j].first]++;
      }
    }
    for (int i = 0; i < MaxV; i++) {
      if (InEdge[i] == 0) {
        firsttask.push(i);
      }
    }
    while (!firsttask.empty()) {
      long long int i = firsttask.front();
      firsttask.pop();
      TopologicalSortdVertex.push_back(i);
      InEdge[i] = -1;
      for (int j = 0; j < Edge[i].size(); j++) {
        InEdge[Edge[i][j].first]--;
        if (InEdge[Edge[i][j].first] == 0) {
          task.push(Edge[i][j].first);
        }
      }
    }
    while (!task.empty()) {
      long long int tmp = task.front();
      TopologicalSortdVertex.push_back(tmp);
      task.pop();
      if (InEdge[tmp] == 0) {
        InEdge[tmp] = -1;
        for (int j = 0; j < Edge[tmp].size(); j++) {
          InEdge[Edge[tmp][j].first]--;
          if (InEdge[Edge[tmp][j].first] == 0) {
            task.push(Edge[tmp][j].first);
          }
        }
      }
    }
  }
};
int main() {
  cout << fixed << setprecision(18);
  cin >> H >> W;
  Graph G(H * W, 0);
  for (long long int i = 0; i < H; i++) {
    cin >> S[i];
  }
  for (long long int i = 0; i < H; i++) {
    for (long long int j = 0; j < W; j++) {
      if (S[i][j] == '#') {
        continue;
      }
      if (i - 1 >= 0) {
        if (S[i - 1][j] != '#') {
          G.Edge[(i)*W + j].push_back({(i - 1) * W + j, 1});
        }
      }
      if (j - 1 >= 0) {
        if (S[i][j - 1] != '#') {
          G.Edge[(i)*W + j].push_back({i * W + j - 1, 1});
        }
      }
      if (i + 1 < H) {
        if (S[i + 1][j] != '#') {
          G.Edge[(i)*W + j].push_back({(i + 1) * W + j, 1});
        }
      }
      if (j + 1 < W) {
        if (S[i][j + 1] != '#') {
          G.Edge[(i)*W + j].push_back({(i)*W + j + 1, 1});
        }
      }
    }
  }
  G.WarshallFloyd();
  res = 0;
  for (long long int i = 0; i < H * W; i++) {
    for (long long int j = 0; j < H * W; j++) {
      if (G.WarshallFloydResult[i][j] != INF) {
        res = max(res, G.WarshallFloydResult[i][j]);
      }
    }
  }
  cout << res << endl;
}
